├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── lib ├── __init__.py ├── api_endpoint │ ├── __init__.py │ ├── api_metadata_processor.py │ ├── api_metamodel2spec.py │ ├── api_type_handler.py │ ├── oas3 │ │ ├── __init__.py │ │ ├── api_metamodel2openapi.py │ │ ├── api_openapi_parameter_handler.py │ │ └── api_openapi_response_handler.py │ └── swagger2 │ │ ├── __init__.py │ │ ├── api_metamodel2swagger.py │ │ ├── api_swagger_parameter_handler.py │ │ └── api_swagger_response_handler.py ├── authentication_metadata_processing.py ├── blacklist_utils.py ├── dictionary_processing.py ├── establish_connection.py ├── file_output_handler.py ├── metadata_processor.py ├── openapi_final_path_processing.py ├── path_processing.py ├── rest_endpoint │ ├── __init__.py │ ├── oas3 │ │ ├── __init__.py │ │ ├── rest_metamodel2openapi.py │ │ ├── rest_openapi_parameter_handler.py │ │ └── rest_openapi_response_handler.py │ ├── rest_deprecation_handler.py │ ├── rest_metadata_processor.py │ ├── rest_metamodel2spec.py │ ├── rest_navigation_handler.py │ ├── rest_type_handler.py │ └── swagger2 │ │ ├── __init__.py │ │ ├── rest_metamodel2swagger.py │ │ ├── rest_swagger_parameter_handler.py │ │ └── rest_swagger_response_handler.py ├── swagger_final_path_processing.py ├── type_handler_common.py └── utils.py ├── test_api_endpoint.py ├── test_api_oas.py ├── test_api_swagger.py ├── test_authentication_metadata_processing.py ├── test_lib.py ├── test_output_handling.py ├── test_rest_deprecation.py ├── test_rest_endpoint.py ├── test_rest_oas.py ├── test_rest_swagger.py ├── test_utils.py └── vmsgen.py /.gitignore: -------------------------------------------------------------------------------- 1 | metamodel 2 | output* 3 | __pycache__ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contributing to vmware-openapi-generator 4 | 5 | The vmware-openapi-generator project team welcomes contributions from the community. Before you start working with vmware-openapi-generator, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. 6 | 7 | ## Contribution Flow 8 | 9 | This is a rough outline of what a contributor's workflow looks like: 10 | 11 | - Create a topic branch from where you want to base your work 12 | - Make commits of logical units 13 | - Make sure your commit messages are in the proper format (see below) 14 | - Push your changes to a topic branch in your fork of the repository 15 | - Submit a pull request 16 | 17 | Example: 18 | 19 | ``` shell 20 | git remote add upstream https://github.com/vmware/vmware-openapi-generator.git 21 | git checkout -b my-new-feature master 22 | git commit -a 23 | git push origin my-new-feature 24 | ``` 25 | 26 | ### Staying In Sync With Upstream 27 | 28 | When your branch gets out of sync with the vmware/master branch, use the following to update: 29 | 30 | ``` shell 31 | git checkout my-new-feature 32 | git fetch -a 33 | git pull --rebase upstream master 34 | git push --force-with-lease origin my-new-feature 35 | ``` 36 | 37 | ### Updating pull requests 38 | 39 | If your PR fails to pass CI or needs changes based on code review, you'll most likely want to squash these changes into 40 | existing commits. 41 | 42 | If your pull request contains a single commit or your changes are related to the most recent commit, you can simply 43 | amend the commit. 44 | 45 | ``` shell 46 | git add . 47 | git commit --amend 48 | git push --force-with-lease origin my-new-feature 49 | ``` 50 | 51 | If you need to squash changes into an earlier commit, you can use: 52 | 53 | ``` shell 54 | git add . 55 | git commit --fixup 56 | git rebase -i --autosquash master 57 | git push --force-with-lease origin my-new-feature 58 | ``` 59 | 60 | Be sure to add a comment to the PR indicating your new changes are ready to review, as GitHub does not generate a 61 | notification when you git push. 62 | 63 | ### Formatting Commit Messages 64 | 65 | We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). 66 | 67 | Be sure to include any related GitHub issue references in the commit message. See 68 | [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues 69 | and commits. 70 | 71 | ## Reporting Bugs and Creating Issues 72 | 73 | When opening a new issue, try to roughly follow the commit message format conventions above. 74 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/LICENSE.txt -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | VMware-openapi-generator 2 | 3 | Copyright (c) 2018 VMware, Inc. 4 | 5 | This product is licensed to you under the MIT license (the "License"). You may not use this product except in compliance with the MIT License. 6 | 7 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmware-openapi-generator 2 | 3 | ## Overview 4 | vmware-openapi-generator generates OpenAPI/Swagger documents from vAPI metamodel format. 5 | 6 | This generator can be used to work with an existing vCenter Server (6.5+) to generate a OpenAPI/Swagger document based on the REST APIs which exist as part of that server. 7 | 8 | ## Try it out 9 | 10 | ### Prerequisites 11 | 12 | * Install VMware vSphere Automation SDK for Python at https://github.com/vmware/vsphere-automation-sdk-python 13 | 14 | 15 | ### Build & Run 16 | 17 | ``` 18 | python vmsgen.py -vc -o 19 | ``` 20 | ``` 21 | Trying to connect https://vcip/api 22 | Connected to https://vcip/api 23 | processing package vcenter 24 | processing package cis 25 | processing package appliance 26 | processing package vapi 27 | processing package content 28 | Generated swagger files at output for https://vcip/api in 106.460405666999577 seconds 29 | ``` 30 | 31 | Running the above command generates the openAPI specification files (3.0) by default. In order to generate the swagger 2.0 specification files, the parameter -oas needs to passed explicitly. 32 | 33 | The description of the input parameters that can go in the run command is as follows: 34 | 1. **metadata-url**: It is the url of the metadata API. If IP address of the vCenter (vcip) is provided, it takes in the values as **https://< vcip >/api** 35 | 2. **rest-navigation-url**: This is the url for rest navigation. If vcip is provided this url becomes **https://< vcip >/rest** 36 | 3. **vcip**: It is the IP Address of the vCenter server. It is used to specify the metadata-url and rest-navigation-url. 37 | 4. **output**: This is output directory where the generated swagger or openapi files will be stored. If the output directory is not supplied, the present working directory is chosen as the output directory. 38 | 5. **tag-seperator**: It is the seperator to be used in tag names i.e. '/'. 39 | 6. **insecure**: It is used to check the SSL certificate validation. If this parameter is supplied as an input argument, it bypasses the certificate validation. If not passed, the program will check for validation. 40 | 7. **unique-operation-ids**: This parameter is passed to generate unique ids for all operation/functions. Default value of this parameter is false. A required semantic rule of the open api specification is that the operations should have a unique operation name even if they are under different paths. If this parameter is ignored the generated swagger file may throw semantic error if it fails the openapi validation. 41 | 8. **metamodel-components**: If this parameter is passed, then each metamodel component retreived from the vCenter server is saved in a different .json file under the metamodel directory. 42 | 9. **host**: It is the IP Address of the host that serves the API. By default the value is < vcenter > 43 | 10. **oas** : This parameter is used to specify as to which version of swagger file the user wants to generate. By default the generated files are of version 3 i.e openapi. If the user wants to generate the version 2 files, the parameter needs to be passed explicitly. 44 | 11. **deprecate-slash-rest**: This parameter is used to deprecate the /rest APIs in the generated OpenAPI specification, only when the API to deprecate has an /api counterpart. 45 | 12. **fetch-authentication-metadata**: Adds security information in the generated OpenAPI definitions. In order to do that it accesses API authentication metadata, which increases the processing time (~20-30 seconds). 46 | 47 | ## Contributing 48 | 49 | The vmware-openapi-generator project team welcomes contributions from the community. Before you start working with vmware-openapi-generator, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. For more detailed information, refer to [CONTRIBUTING.md](CONTRIBUTING.md). 50 | 51 | ## License 52 | MIT License 53 | 54 | Copyright (c) 2016-2020 VMware, Inc. 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 57 | 58 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 61 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | from .api_endpoint.api_metadata_processor import ApiMetadataProcessor 2 | from .rest_endpoint.rest_metadata_processor import RestMetadataProcessor 3 | -------------------------------------------------------------------------------- /lib/api_endpoint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/api_endpoint/__init__.py -------------------------------------------------------------------------------- /lib/api_endpoint/api_metadata_processor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | 6 | from lib import utils 7 | from lib.metadata_processor import MetadataProcessor 8 | from .oas3.api_metamodel2openapi import ApiMetamodel2Openapi 9 | from .swagger2.api_metamodel2swagger import ApiMetamodel2Swagger 10 | 11 | 12 | openapi = ApiMetamodel2Openapi() 13 | swagg = ApiMetamodel2Swagger() 14 | 15 | 16 | class ApiMetadataProcessor(MetadataProcessor): 17 | 18 | def __init__(self): 19 | pass 20 | 21 | def get_path_and_type_dicts( 22 | self, 23 | package_name, 24 | service_urls, 25 | structure_dict, 26 | enum_dict, 27 | service_dict, 28 | service_url_dict, 29 | http_error_map, 30 | show_unreleased_apis, 31 | spec, 32 | auth_navigator=None): 33 | 34 | print('processing package ' + package_name + os.linesep) 35 | type_dict = {} 36 | path_list = [] 37 | for service_url in service_urls: 38 | service_name, service_end_point = service_url_dict.get( 39 | service_url, None) 40 | service_info = service_dict.get(service_name, None) 41 | if service_info is None: 42 | continue 43 | if (not show_unreleased_apis) and utils.is_filtered(service_info.metadata): 44 | continue 45 | for operation_id, operation_info in service_info.operations.items(): 46 | 47 | method, url = self.api_get_url_and_method( 48 | operation_info.metadata) 49 | if method is None or url is None: 50 | continue 51 | 52 | # check for query parameters 53 | if 'params' in operation_info.metadata[method].elements: 54 | element_value = operation_info.metadata[method].elements['params'] 55 | params = "&".join(element_value.list_value) 56 | url = url + '?' + params 57 | 58 | if spec == '2': 59 | path = swagg.get_path( 60 | operation_info, 61 | method, 62 | url, 63 | service_name, 64 | type_dict, 65 | structure_dict, 66 | enum_dict, 67 | operation_id, 68 | http_error_map, 69 | show_unreleased_apis) 70 | if auth_navigator is not None: 71 | scheme_set = auth_navigator.find_schemes_set(operation_id, service_name, package_name) 72 | if scheme_set is not None and len(scheme_set) != 0: 73 | swagg.decorate_path_with_security(path, scheme_set) 74 | if spec == '3': 75 | path = openapi.get_path( 76 | operation_info, 77 | method, 78 | url, 79 | service_name, 80 | type_dict, 81 | structure_dict, 82 | enum_dict, 83 | operation_id, 84 | http_error_map, 85 | show_unreleased_apis) 86 | 87 | path_list.append(path) 88 | continue 89 | 90 | path_dict = self.convert_path_list_to_path_map(path_list) 91 | self.cleanup(path_dict=path_dict, type_dict=type_dict) 92 | return path_dict, type_dict 93 | 94 | def api_get_url_and_method(self, metadata): 95 | for method in metadata.keys(): 96 | if method in ['POST', 'GET', 'DELETE', 'PUT', 'PATCH']: 97 | url_path = metadata[method].elements["path"].string_value 98 | url_path = "/api" + url_path 99 | return method, url_path 100 | return None, None 101 | -------------------------------------------------------------------------------- /lib/api_endpoint/api_metamodel2spec.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | 6 | 7 | class ApiMetamodel2Spec(): 8 | 9 | def get_path( 10 | self, 11 | operation_info, 12 | http_method, 13 | url, 14 | service_name, 15 | type_dict, 16 | structure_dict, 17 | enum_dict, 18 | operation_id, 19 | error_map, 20 | show_unreleased_apis): 21 | pass 22 | 23 | def handle_request_mapping( 24 | self, 25 | url, 26 | method_type, 27 | service_name, 28 | operation_name, 29 | params_metadata, 30 | content_type, 31 | type_dict, 32 | structure_svc, 33 | enum_svc, 34 | show_unreleased_apis, 35 | spec): 36 | if method_type in ('post', 'put', 'patch'): 37 | return self.process_put_post_patch_request( 38 | url, 39 | service_name, 40 | operation_name, 41 | params_metadata, 42 | content_type, 43 | type_dict, 44 | structure_svc, 45 | enum_svc, 46 | show_unreleased_apis, 47 | spec) 48 | if method_type == 'get': 49 | return self.process_get_request( 50 | url, 51 | params_metadata, 52 | type_dict, 53 | structure_svc, 54 | enum_svc, 55 | show_unreleased_apis, 56 | spec) 57 | if method_type == 'delete': 58 | return self.process_delete_request( 59 | url, 60 | params_metadata, 61 | type_dict, 62 | structure_svc, 63 | enum_svc, 64 | show_unreleased_apis, 65 | spec) 66 | 67 | def process_put_post_patch_request( 68 | self, 69 | url, 70 | service_name, 71 | operation_name, 72 | params, 73 | content_type, 74 | type_dict, 75 | structure_svc, 76 | enum_svc, 77 | show_unreleased_apis, 78 | spec): 79 | """ 80 | Handles http post/put/patch request. 81 | todo: handle query, formData and header parameters 82 | """ 83 | # Path 84 | path_param_list, other_param_list, new_url = utils.extract_path_parameters( 85 | params, url) 86 | par_array = [] 87 | for field_info in path_param_list: 88 | parx = spec.convert_field_info_to_swagger_parameter( 89 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 90 | par_array.append(parx) 91 | 92 | # Body 93 | body_param_list, other_param_list = utils.extract_body_parameters( 94 | other_param_list) 95 | 96 | if body_param_list: 97 | parx = spec.wrap_body_params( 98 | service_name, 99 | operation_name, 100 | content_type, 101 | body_param_list, 102 | type_dict, 103 | structure_svc, 104 | enum_svc, 105 | show_unreleased_apis) 106 | if parx is not None: 107 | if isinstance(parx, list): 108 | par_array.extend(parx) 109 | else: 110 | par_array.append(parx) 111 | 112 | # Query 113 | query_param_list, other_param_list = utils.extract_query_parameters( 114 | other_param_list) 115 | for query_param in query_param_list: 116 | parx = spec.convert_field_info_to_swagger_parameter( 117 | 'query', query_param, type_dict, structure_svc, enum_svc, show_unreleased_apis) 118 | par_array.append(parx) 119 | 120 | # process query parameters 121 | for field_info in other_param_list: 122 | # See documentation of method flatten_query_param_spec to understand 123 | # handling of all the query parameters; filter as well as non 124 | # filter 125 | flattened_params = spec.flatten_query_param_spec( 126 | field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 127 | if flattened_params is not None: 128 | par_array = par_array + flattened_params 129 | 130 | return par_array, new_url 131 | 132 | def process_get_request( 133 | self, 134 | url, 135 | params, 136 | type_dict, 137 | structure_svc, 138 | enum_svc, 139 | show_unreleased_apis, 140 | spec): 141 | param_array = [] 142 | path_param_list, other_params_list, new_url = utils.extract_path_parameters( 143 | params, url) 144 | 145 | for field_info in path_param_list: 146 | parameter_obj = spec.convert_field_info_to_swagger_parameter( 147 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 148 | param_array.append(parameter_obj) 149 | 150 | query_param_list, other_params_list = utils.extract_query_parameters( 151 | other_params_list) 152 | # Query 153 | for query_param in query_param_list: 154 | parameter_obj = spec.convert_field_info_to_swagger_parameter( 155 | 'query', query_param, type_dict, structure_svc, enum_svc, show_unreleased_apis) 156 | param_array.append(parameter_obj) 157 | 158 | # process query parameters 159 | for field_info in other_params_list: 160 | # See documentation of method flatten_query_param_spec to understand 161 | # handling of all the query parameters; filter as well as non 162 | # filter 163 | flattened_params = spec.flatten_query_param_spec( 164 | field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 165 | if flattened_params is not None: 166 | param_array = param_array + flattened_params 167 | return param_array, new_url 168 | 169 | def process_delete_request( 170 | self, 171 | url, 172 | params, 173 | type_dict, 174 | structure_svc, 175 | enum_svc, 176 | show_unreleased_apis, 177 | spec): 178 | path_param_list, other_params, new_url = utils.extract_path_parameters( 179 | params, url) 180 | param_array = [] 181 | for field_info in path_param_list: 182 | parx = spec.convert_field_info_to_swagger_parameter( 183 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 184 | param_array.append(parx) 185 | for field_info in other_params: 186 | parx = spec.convert_field_info_to_swagger_parameter( 187 | 'query', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 188 | param_array.append(parx) 189 | return param_array, new_url 190 | 191 | def post_process_path(self, path_obj): 192 | pass 193 | -------------------------------------------------------------------------------- /lib/api_endpoint/api_type_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | from lib.type_handler_common import TypeHandlerCommon 6 | 7 | 8 | class ApiTypeHandler(TypeHandlerCommon): 9 | 10 | def __init__(self, show_unreleased_apis): 11 | TypeHandlerCommon.__init__(self, show_unreleased_apis) 12 | 13 | def visit_generic( 14 | self, 15 | generic_instantiation, 16 | new_prop, 17 | type_dict, 18 | structure_svc, 19 | enum_svc, 20 | ref_path): 21 | if generic_instantiation.generic_type == 'OPTIONAL': 22 | new_prop['required'] = False 23 | self.visit_type_category( 24 | generic_instantiation.element_type, 25 | new_prop, 26 | type_dict, 27 | structure_svc, 28 | enum_svc, 29 | ref_path) 30 | elif generic_instantiation.generic_type == 'LIST': 31 | new_prop['type'] = 'array' 32 | self.visit_type_category( 33 | generic_instantiation.element_type, 34 | new_prop, 35 | type_dict, 36 | structure_svc, 37 | enum_svc, 38 | ref_path) 39 | elif generic_instantiation.generic_type == 'SET': 40 | new_prop['type'] = 'array' 41 | new_prop['uniqueItems'] = True 42 | self.visit_type_category( 43 | generic_instantiation.element_type, 44 | new_prop, 45 | type_dict, 46 | structure_svc, 47 | enum_svc, 48 | ref_path) 49 | elif generic_instantiation.generic_type == 'MAP': 50 | # Have static key/value pair object maping for /rest paths 51 | # while use additionalProperties for /api paths 52 | new_type = {'type': 'object', 'additionalProperties': {}} 53 | 54 | if generic_instantiation.map_value_type.category == 'USER_DEFINED': 55 | new_type['additionalProperties'] = { 56 | '$ref': ref_path + utils.get_str_camel_case( 57 | generic_instantiation.map_value_type.user_defined_type.resource_id, 58 | *utils.CAMELCASE_SEPARATOR_LIST)} 59 | res_type = generic_instantiation.map_value_type.user_defined_type.resource_type 60 | res_id = generic_instantiation.map_value_type.user_defined_type.resource_id 61 | self.check_type( 62 | res_type, 63 | res_id, 64 | type_dict, 65 | structure_svc, 66 | enum_svc, 67 | ref_path) 68 | 69 | elif generic_instantiation.map_value_type.category == 'BUILTIN': 70 | new_type['additionalProperties'] = { 71 | 'type': utils.metamodel_to_swagger_type_converter( 72 | generic_instantiation.map_value_type.builtin_type)[0]} 73 | 74 | elif generic_instantiation.map_value_type.category == 'GENERIC': 75 | temp_new_type = {} 76 | self.visit_generic( 77 | generic_instantiation.map_value_type.generic_instantiation, 78 | temp_new_type, 79 | type_dict, 80 | structure_svc, 81 | enum_svc, 82 | ref_path) 83 | new_type['additionalProperties'] = temp_new_type 84 | 85 | new_prop.update(new_type) 86 | 87 | if 'additionalProperties' in new_type: 88 | if not new_type['additionalProperties'].get('required', True): 89 | del new_type['additionalProperties']['required'] 90 | 91 | if '$ref' in new_prop: 92 | del new_prop['$ref'] 93 | 94 | def check_type( 95 | self, 96 | resource_type, 97 | type_name, 98 | type_dict, 99 | structure_svc, 100 | enum_svc, 101 | ref_path): 102 | camel_cased_type_name = utils.get_str_camel_case(type_name, *utils.CAMELCASE_SEPARATOR_LIST) 103 | if camel_cased_type_name in type_dict or utils.is_type_builtin(type_name): 104 | return 105 | if resource_type == 'com.vmware.vapi.structure': 106 | structure_info = self.get_structure_info(type_name, structure_svc) 107 | if structure_info is not None: 108 | # Mark it as visited to handle recursive definitions. (Type A 109 | # referring to Type A in one of the fields). 110 | type_dict[camel_cased_type_name] = {} 111 | self.process_structure_info( 112 | camel_cased_type_name, 113 | structure_info, 114 | type_dict, 115 | structure_svc, 116 | enum_svc, 117 | ref_path) 118 | else: 119 | enum_info = self.get_enum_info(type_name, enum_svc) 120 | if enum_info is not None: 121 | # Mark it as visited to handle recursive definitions. (Type A 122 | # referring to Type A in one of the fields). 123 | type_dict[camel_cased_type_name] = {} 124 | self.process_enum_info( 125 | camel_cased_type_name, enum_info, type_dict) 126 | 127 | def visit_user_defined( 128 | self, 129 | user_defined_type, 130 | newprop, 131 | type_dict, 132 | structure_svc, 133 | enum_svc, 134 | ref_path): 135 | if user_defined_type.resource_id is None: 136 | return 137 | camel_cased_ref = utils.get_str_camel_case(user_defined_type.resource_id, 138 | *utils.CAMELCASE_SEPARATOR_LIST) 139 | if 'type' in newprop and newprop['type'] == 'array': 140 | item_obj = {'$ref': ref_path + camel_cased_ref} 141 | newprop['items'] = item_obj 142 | # if not array, fill in type or ref 143 | else: 144 | newprop['$ref'] = ref_path + camel_cased_ref 145 | 146 | self.check_type( 147 | user_defined_type.resource_type, 148 | user_defined_type.resource_id, 149 | type_dict, 150 | structure_svc, 151 | enum_svc, 152 | ref_path) -------------------------------------------------------------------------------- /lib/api_endpoint/oas3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/api_endpoint/oas3/__init__.py -------------------------------------------------------------------------------- /lib/api_endpoint/oas3/api_metamodel2openapi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | from lib.api_endpoint.api_metamodel2spec import ApiMetamodel2Spec 6 | from .api_openapi_parameter_handler import ApiOpenapiParaHandler 7 | from .api_openapi_response_handler import ApiOpenapiRespHandler 8 | 9 | api_open_ph = ApiOpenapiParaHandler() 10 | api_open_rh = ApiOpenapiRespHandler() 11 | 12 | 13 | class ApiMetamodel2Openapi(ApiMetamodel2Spec): 14 | def get_path( 15 | self, 16 | operation_info, 17 | http_method, 18 | url, 19 | service_name, 20 | type_dict, 21 | structure_dict, 22 | enum_dict, 23 | operation_id, 24 | http_error_map, 25 | show_unreleased_apis): 26 | documentation = operation_info.documentation 27 | op_metadata = operation_info.metadata 28 | params = operation_info.params 29 | errors = operation_info.errors 30 | output = operation_info.output 31 | http_method = http_method.lower() 32 | par_array, url = self.handle_request_mapping(url, http_method, service_name, 33 | operation_id, params, type_dict, 34 | structure_dict, enum_dict, show_unreleased_apis, api_open_ph) 35 | response_map = api_open_rh.populate_response_map( 36 | output, 37 | errors, 38 | http_error_map, 39 | type_dict, 40 | structure_dict, 41 | enum_dict, 42 | service_name, 43 | operation_id, 44 | op_metadata, 45 | show_unreleased_apis) 46 | 47 | path_obj = utils.build_path( 48 | service_name, 49 | http_method, 50 | url, 51 | documentation, 52 | par_array, 53 | operation_id=operation_id, 54 | responses=response_map) 55 | 56 | utils.create_req_body_from_params_list(path_obj) 57 | self.post_process_path(path_obj) 58 | path = utils.add_basic_auth(path_obj) 59 | return path 60 | 61 | def post_process_path(self, path_obj): 62 | # Temporary fixes necessary for generated spec files. 63 | # Hardcoding for now as it is not available from metadata. 64 | if path_obj['path'] == '/com/vmware/cis/session' and path_obj['method'] == 'post': 65 | header_parameter = { 66 | 'in': 'header', 67 | 'required': True, 68 | 'type': 'string', 69 | 'name': 'vmware-use-header-authn', 70 | 'description': 'Custom header to protect against CSRF attacks in browser based clients'} 71 | header_parameter['schema'] = {'type': 'string'} 72 | path_obj['parameters'] = [header_parameter] 73 | 74 | # Allow invoking $task operations from the api-explorer 75 | if path_obj['operationId'].endswith('$task'): 76 | path_obj['path'] = utils.add_query_param( 77 | path_obj['path'], 'vmw-task=true') 78 | -------------------------------------------------------------------------------- /lib/api_endpoint/oas3/api_openapi_parameter_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | 6 | from lib import utils 7 | from lib.api_endpoint.api_type_handler import ApiTypeHandler 8 | 9 | 10 | class ApiOpenapiParaHandler(): 11 | 12 | def convert_field_info_to_swagger_parameter( 13 | self, 14 | param_type, 15 | input_parameter_obj, 16 | type_dict, 17 | structure_svc, 18 | enum_svc, 19 | show_unreleased_apis): 20 | """ 21 | Converts metamodel fieldinfo to swagger parameter. 22 | """ 23 | parameter_obj = {} 24 | ref_path = "#/components/schemas/" 25 | tpHandler = ApiTypeHandler(show_unreleased_apis) 26 | tpHandler.visit_type_category( 27 | input_parameter_obj.type, 28 | parameter_obj, 29 | type_dict, 30 | structure_svc, 31 | enum_svc, 32 | ref_path) 33 | if 'required' not in parameter_obj: 34 | parameter_obj['required'] = True 35 | parameter_obj['in'] = param_type 36 | parameter_obj['name'] = input_parameter_obj.name 37 | parameter_obj['description'] = input_parameter_obj.documentation 38 | # $ref should be encapsulated in 'schema' instead of parameter. -> this throws swagger validation error 39 | # hence another method is to to get data in $ref in parameter_obj 40 | # itself 41 | if '$ref' in parameter_obj: 42 | type_obj = type_dict[parameter_obj['$ref'][len(ref_path):]] 43 | description = parameter_obj['description'] 44 | if 'description' in type_obj: 45 | description = "" 46 | description = "{ 1. " + type_obj['description'] + \ 47 | " }, { 2. " + parameter_obj['description'] + " }" 48 | parameter_obj.update(type_obj) 49 | parameter_obj['description'] = description 50 | del parameter_obj['$ref'] 51 | 52 | if 'type' in parameter_obj: 53 | temp_schema = {'type': parameter_obj['type']} 54 | parameter_obj['schema'] = temp_schema 55 | del parameter_obj['type'] 56 | 57 | return parameter_obj 58 | 59 | def wrap_body_params( 60 | self, 61 | service_name, 62 | operation_name, 63 | content_type, 64 | body_param_list, 65 | type_dict, 66 | structure_svc, 67 | enum_svc, 68 | show_unreleased_apis): 69 | """ 70 | Creates a json object wrapper around request body parameters. parameter names are used as keys and the 71 | parameters as values. 72 | For instance, datacenter create operation takes CreateSpec whose parameter name is spec. 73 | This method creates a json wrapper object 74 | datacenter.create { 75 | 'spec' : {spec obj representation } 76 | } 77 | """ 78 | # todo: 79 | # form-url-encoded support; not utilized content_type 80 | # todo: 81 | # not unique enough. make it unique 82 | wrapper_name = utils.get_str_camel_case(service_name + '_' + operation_name, *utils.CAMELCASE_SEPARATOR_LIST) 83 | body_obj = {} 84 | properties_obj = {} 85 | required = [] 86 | ref_path = "#/components/schemas/" 87 | tpHandler = ApiTypeHandler(show_unreleased_apis) 88 | for param in body_param_list: 89 | parameter_obj = {} 90 | tpHandler.visit_type_category( 91 | param.type, 92 | parameter_obj, 93 | type_dict, 94 | structure_svc, 95 | enum_svc, 96 | ref_path) 97 | parameter_obj['description'] = param.documentation 98 | body_obj.update(parameter_obj) 99 | 100 | if 'requestBodies' not in type_dict: 101 | type_dict['requestBodies'] = {} 102 | type_dict['requestBodies'][wrapper_name] = { 103 | 'content': { 104 | 'application/json': { 105 | 'schema': { 106 | '$ref': ref_path + wrapper_name 107 | } 108 | } 109 | } 110 | } 111 | type_dict[wrapper_name] = body_obj 112 | parameter_obj = {'$ref': "#/components/requestBodies/" + wrapper_name} 113 | 114 | return parameter_obj 115 | 116 | def flatten_query_param_spec( 117 | self, 118 | query_param_info, 119 | type_dict, 120 | structure_svc, 121 | enum_svc, 122 | show_unreleased_apis): 123 | """ 124 | Flattens query parameters specs. 125 | 1. Create a query parameter for every field in spec. 126 | Example 1: 127 | consider datacenter get which accepts optional filterspec. 128 | Optional filter) 129 | The method would convert the filterspec to 3 separate query parameters 130 | filter.datacenters, filter.names and filter.folders. 131 | Example 2: 132 | consider /vcenter/deployment/install/initial-config/remote-psc/thumbprint get 133 | which accepts parameter 134 | vcenter.deployment.install.initial_config.remote_psc.thumbprint.remote_spec. 135 | The two members defined under remote_spec 136 | address and https_port are converted to two separate query parameters 137 | address(required) and https_port(optional). 138 | 2. The field info is simple type. i.e the type is string, integer 139 | then it is converted it to swagger parameter. 140 | Example: 141 | consider /com/vmware/content/library/item get 142 | which accepts parameter 'library_id'. The field is converted 143 | to library_id query parameter. 144 | 3. This field has references to a spec but the spec is not 145 | a complex type and does not have property 'properties'. 146 | i.e the type is string, integer. The members defined under the spec are 147 | converted to query parameter. 148 | Example: 149 | consider /appliance/update/pending get which accepts two parameter 150 | 'source_type' and url. Where source_type is defined in the spec 151 | 'appliance.update.pending.source_type' and field url 152 | is of type string. 153 | The fields 'source_type' and 'url' are converted to query parameter 154 | of type string. 155 | """ 156 | prop_array = [] 157 | parameter_obj = {} 158 | ref_path = "#/components/schemas/" 159 | tpHandler = ApiTypeHandler(show_unreleased_apis) 160 | tpHandler.visit_type_category( 161 | query_param_info.type, 162 | parameter_obj, 163 | type_dict, 164 | structure_svc, 165 | enum_svc, 166 | ref_path) 167 | if '$ref' in parameter_obj: 168 | reference = parameter_obj['$ref'].replace(ref_path, '') 169 | type_ref = type_dict.get(reference, None) 170 | if type_ref is None: 171 | return None 172 | if 'properties' in type_ref: 173 | for property_name, property_value in six.iteritems( 174 | type_ref['properties']): 175 | prop = {'in': 'query', 'name': property_name} 176 | prop['schema'] = {} 177 | if 'type' in property_value: 178 | prop['schema']['type'] = property_value['type'] 179 | if property_value['type'] == 'array': 180 | prop['schema']['items'] = property_value['items'] 181 | if '$ref' in property_value['items']: 182 | ref = property_value['items']['$ref'].replace( 183 | ref_path, '') 184 | type_ref = type_dict[ref] 185 | prop['schema']['items'] = type_ref 186 | if 'description' in prop['schema']['items']: 187 | del prop['schema']['items']['description'] 188 | if 'description' in property_value: 189 | prop['description'] = property_value['description'] 190 | elif '$ref' in property_value: 191 | reference = property_value['$ref'].replace( 192 | ref_path, '') 193 | prop_obj = type_dict[reference] 194 | prop['schema'] = prop_obj 195 | if 'required' in type_ref: 196 | if property_name in type_ref['required']: 197 | prop['required'] = True 198 | else: 199 | prop['required'] = False 200 | prop_array.append(prop) 201 | else: 202 | prop = { 203 | 'in': 'query', 204 | 'name' : query_param_info.name, 205 | 'description': type_ref['description'], 206 | 'schema': type_ref} 207 | prop_array.append(prop) 208 | else: 209 | parameter_obj['in'] = 'query' 210 | parameter_obj['name'] = query_param_info.name 211 | parameter_obj['description'] = query_param_info.documentation 212 | if 'required' not in parameter_obj: 213 | parameter_obj['required'] = True 214 | parameter_obj['schema'] = {"type": parameter_obj['type']} 215 | del parameter_obj['type'] 216 | 217 | prop_array.append(parameter_obj) 218 | return prop_array 219 | -------------------------------------------------------------------------------- /lib/api_endpoint/oas3/api_openapi_response_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import requests 5 | from six.moves import http_client 6 | 7 | from lib import utils 8 | from lib.api_endpoint.api_type_handler import ApiTypeHandler 9 | 10 | 11 | class ApiOpenapiRespHandler(): 12 | 13 | def populate_response_map( 14 | self, 15 | output, 16 | errors, 17 | http_error_map, 18 | type_dict, 19 | structure_svc, 20 | enum_svc, 21 | service_id, 22 | operation_id, 23 | op_metadata, 24 | show_unreleased_apis): 25 | 26 | response_map = {} 27 | ref_path = "#/components/schemas/" 28 | success_response = { 29 | 'description': output.documentation, 30 | 'content': { 31 | 'application/json': { 32 | } 33 | } 34 | } 35 | schema = {} 36 | tpHandler = ApiTypeHandler(show_unreleased_apis) 37 | tpHandler.visit_type_category( 38 | output.type, 39 | schema, 40 | type_dict, 41 | structure_svc, 42 | enum_svc, 43 | ref_path) 44 | # if type of schema is void, don't include it. 45 | # this prevents showing response as void in swagger-ui 46 | if schema is not None: 47 | if not ('type' in schema and schema['type'] == 'void'): 48 | resp = schema 49 | success_response['content']['application/json']['schema'] = resp 50 | # success response is not mapped through metamodel. 51 | # hardcode it for now. 52 | success_response_code = requests.codes.ok 53 | if 'Response' in op_metadata and op_metadata['Response'] is not None: 54 | success_response_code = int(op_metadata['Response'].elements['code'].string_value) 55 | response_map[success_response_code] = success_response 56 | 57 | for error in errors: 58 | status_code = http_error_map.error_api_map.get( 59 | error.structure_id, 60 | http_client.INTERNAL_SERVER_ERROR) 61 | tpHandler.check_type( 62 | 'com.vmware.vapi.structure', 63 | error.structure_id, 64 | type_dict, 65 | structure_svc, 66 | enum_svc, 67 | ref_path) 68 | response_obj = { 69 | 'description': error.documentation, 70 | 'content': { 71 | 'application/json': { 72 | 'schema': {'$ref': ref_path + utils.get_str_camel_case( 73 | error.structure_id, *utils.CAMELCASE_SEPARATOR_LIST)} 74 | } 75 | } 76 | } 77 | response_map[status_code] = response_obj 78 | return response_map 79 | -------------------------------------------------------------------------------- /lib/api_endpoint/swagger2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/api_endpoint/swagger2/__init__.py -------------------------------------------------------------------------------- /lib/api_endpoint/swagger2/api_metamodel2swagger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils, authentication_metadata_processing 5 | from lib.api_endpoint.api_metamodel2spec import ApiMetamodel2Spec 6 | from .api_swagger_parameter_handler import ApiSwaggerParaHandler 7 | from .api_swagger_response_handler import ApiSwaggerRespHandler 8 | 9 | api_swagg_ph = ApiSwaggerParaHandler() 10 | api_swagg_rh = ApiSwaggerRespHandler() 11 | 12 | 13 | class ApiMetamodel2Swagger(ApiMetamodel2Spec): 14 | def get_path( 15 | self, 16 | operation_info, 17 | http_method, 18 | url, 19 | service_name, 20 | type_dict, 21 | structure_dict, 22 | enum_dict, 23 | operation_id, 24 | http_error_map, 25 | show_unreleased_apis): 26 | documentation = operation_info.documentation 27 | op_metadata = operation_info.metadata 28 | method_info = op_metadata[http_method] 29 | content_type = method_info.elements["consumes"].string_value if "consumes" in method_info.elements else None 30 | 31 | params = operation_info.params 32 | errors = operation_info.errors 33 | output = operation_info.output 34 | http_method = http_method.lower() 35 | par_array, url = self.handle_request_mapping(url, http_method, service_name, 36 | operation_id, params, content_type, type_dict, 37 | structure_dict, enum_dict, show_unreleased_apis, api_swagg_ph) 38 | response_map = api_swagg_rh.populate_response_map( 39 | output, 40 | errors, 41 | http_error_map, 42 | type_dict, 43 | structure_dict, 44 | enum_dict, 45 | service_name, 46 | operation_id, 47 | op_metadata, 48 | show_unreleased_apis) 49 | 50 | consumes = None 51 | if content_type == 'FORM_URLENCODED': 52 | consumes = ["application/x-www-form-urlencoded"] 53 | path_obj = utils.build_path( 54 | service_name, 55 | http_method, 56 | url, 57 | documentation, 58 | par_array, 59 | operation_id=operation_id, 60 | responses=response_map, 61 | consumes=consumes) 62 | self.post_process_path(path_obj) 63 | path = utils.add_basic_auth(path_obj) 64 | return path 65 | 66 | def post_process_path(self, path_obj): 67 | # Temporary fixes necessary for generated spec files. 68 | # Hardcoding for now as it is not available from metadata. 69 | if path_obj['path'] == '/com/vmware/cis/session' and path_obj['method'] == 'post': 70 | header_parameter = { 71 | 'in': 'header', 72 | 'required': True, 73 | 'type': 'string', 74 | 'name': 'vmware-use-header-authn', 75 | 'description': 'Custom header to protect against CSRF attacks in browser based clients'} 76 | header_parameter['type'] = 'string' 77 | path_obj['parameters'] = [header_parameter] 78 | 79 | # Allow invoking $task operations from the api-explorer 80 | if path_obj['operationId'].endswith('$task'): 81 | path_obj['path'] = utils.add_query_param( 82 | path_obj['path'], 'vmw-task=true') 83 | 84 | def decorate_path_with_security(self, path_obj, scheme_set): 85 | security_schemes = [] 86 | if authentication_metadata_processing.session_id_scheme in scheme_set: 87 | security_schemes.append({"session_id": []}) 88 | if authentication_metadata_processing.basic_auth_scheme in scheme_set: 89 | security_schemes.append({"basic_auth": []}) 90 | if security_schemes: 91 | path_obj["security"] = security_schemes 92 | -------------------------------------------------------------------------------- /lib/api_endpoint/swagger2/api_swagger_parameter_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | from lib import utils 6 | from lib.api_endpoint.api_type_handler import ApiTypeHandler 7 | 8 | 9 | class ApiSwaggerParaHandler(): 10 | 11 | def convert_field_info_to_swagger_parameter( 12 | self, 13 | param_type, 14 | input_parameter_obj, 15 | type_dict, 16 | structure_svc, 17 | enum_svc, 18 | show_unreleased_apis): 19 | """ 20 | Converts metamodel fieldinfo to swagger parameter. 21 | """ 22 | parameter_obj = {} 23 | ref_path = "#/definitions/" 24 | tpHandler = ApiTypeHandler(show_unreleased_apis) 25 | tpHandler.visit_type_category( 26 | input_parameter_obj.type, 27 | parameter_obj, 28 | type_dict, 29 | structure_svc, 30 | enum_svc, 31 | ref_path) 32 | if 'required' not in parameter_obj: 33 | parameter_obj['required'] = True 34 | parameter_obj['in'] = param_type 35 | parameter_obj['name'] = input_parameter_obj.name 36 | parameter_obj['description'] = input_parameter_obj.documentation 37 | # $ref should be encapsulated in 'schema' instead of parameter. -> this throws swagger validation error 38 | # hence another method is to to get data in $ref in parameter_obj 39 | # itself 40 | if '$ref' in parameter_obj: 41 | type_obj = type_dict[parameter_obj['$ref'][len(ref_path):]] 42 | description = parameter_obj['description'] 43 | if 'description' in type_obj: 44 | description = "" 45 | description = "{ 1. " + type_obj['description'] + \ 46 | " }, { 2. " + parameter_obj['description'] + " }" 47 | parameter_obj.update(type_obj) 48 | parameter_obj['description'] = description 49 | del parameter_obj['$ref'] 50 | return parameter_obj 51 | 52 | def wrap_body_params( 53 | self, 54 | service_name, 55 | operation_name, 56 | content_type, 57 | body_param_list, 58 | type_dict, 59 | structure_svc, 60 | enum_svc, 61 | show_unreleased_apis): 62 | """ 63 | Creates a json object wrapper around request body parameters. parameter names are used as keys and the 64 | parameters as values. 65 | For instance, datacenter create operation takes CreateSpec whose parameter name is spec. 66 | This method creates a json wrapper object 67 | datacenter.create { 68 | 'spec' : {spec obj representation } 69 | } 70 | """ 71 | # todo: 72 | # not unique enough. make it unique 73 | wrapper_name = utils.get_str_camel_case(service_name + '_' + operation_name, *utils.CAMELCASE_SEPARATOR_LIST) 74 | body_obj = {} 75 | properties_obj = {} 76 | required = [] 77 | ref_path = "#/definitions/" 78 | tpHandler = ApiTypeHandler(show_unreleased_apis) 79 | for param in body_param_list: 80 | parameter_obj = {} 81 | tpHandler.visit_type_category( 82 | param.type, 83 | parameter_obj, 84 | type_dict, 85 | structure_svc, 86 | enum_svc, 87 | ref_path) 88 | parameter_obj['description'] = param.documentation 89 | if 'BodyField' in param.metadata: 90 | body_obj['type'] = 'object' 91 | body_obj['properties'] = properties_obj 92 | properties_obj[param.metadata['BodyField'].elements['name'].string_value] = parameter_obj 93 | if 'required' not in parameter_obj: 94 | required.append(param.name) 95 | elif parameter_obj['required'] == 'true': 96 | required.append(param.name) 97 | else: 98 | body_obj.update(parameter_obj) 99 | 100 | parameter_obj = {'in': 'body', 'name': 'request_body'} 101 | if len(required) > 0: 102 | body_obj['required'] = required 103 | parameter_obj['required'] = True 104 | elif 'required' in body_obj: 105 | del body_obj['required'] 106 | 107 | type_dict[wrapper_name] = body_obj 108 | 109 | if content_type == 'FORM_URLENCODED': 110 | return self.wrap_form_data_params(type_dict, wrapper_name) 111 | 112 | schema_obj = {'$ref': ref_path + wrapper_name} 113 | parameter_obj['schema'] = schema_obj 114 | return parameter_obj 115 | 116 | def wrap_form_data_params(self, type_dict, wrapper_name): 117 | parameter_list = [] 118 | definition = type_dict[wrapper_name] 119 | if "properties" in definition: 120 | for property_name, property_value in definition["properties"].items(): 121 | formDataEntry = {"in": "formData", 122 | "name": property_name} 123 | formDataEntry.update({k: v for k, v in property_value.items() if k in ['type', 'description']}) 124 | if "required" in definition and property_name in definition["required"]: 125 | formDataEntry["required"] = "true"; 126 | parameter_list.append(formDataEntry) 127 | elif "$ref" in definition: 128 | reference = definition["$ref"].replace("#/definitions/", "") 129 | return self.wrap_form_data_params(type_dict, reference) 130 | return parameter_list 131 | 132 | def flatten_query_param_spec( 133 | self, 134 | query_param_info, 135 | type_dict, 136 | structure_svc, 137 | enum_svc, 138 | show_unreleased_apis): 139 | """ 140 | Flattens query parameters specs. 141 | 1. Create a query parameter for every field in spec. 142 | Example 1: 143 | consider datacenter get which accepts optional filterspec. 144 | Optional filter) 145 | The method would convert the filterspec to 3 separate query parameters 146 | filter.datacenters, filter.names and filter.folders. 147 | Example 2: 148 | consider /vcenter/deployment/install/initial-config/remote-psc/thumbprint get 149 | which accepts parameter 150 | vcenter.deployment.install.initial_config.remote_psc.thumbprint.remote_spec. 151 | The two members defined under remote_spec 152 | address and https_port are converted to two separate query parameters 153 | address(required) and https_port(optional). 154 | 2. The field info is simple type. i.e the type is string, integer 155 | then it is converted it to swagger parameter. 156 | Example: 157 | consider /com/vmware/content/library/item get 158 | which accepts parameter 'library_id'. The field is converted 159 | to library_id query parameter. 160 | 3. This field has references to a spec but the spec is not 161 | a complex type and does not have property 'properties'. 162 | i.e the type is string, integer. The members defined under the spec are 163 | converted to query parameter. 164 | Example: 165 | consider /appliance/update/pending get which accepts two parameter 166 | 'source_type' and url. Where source_type is defined in the spec 167 | 'appliance.update.pending.source_type' and field url 168 | is of type string. 169 | The fields 'source_type' and 'url' are converted to query parameter 170 | of type string. 171 | """ 172 | prop_array = [] 173 | parameter_obj = {} 174 | ref_path = "#/definitions/" 175 | tpHandler = ApiTypeHandler(show_unreleased_apis) 176 | tpHandler.visit_type_category( 177 | query_param_info.type, 178 | parameter_obj, 179 | type_dict, 180 | structure_svc, 181 | enum_svc, 182 | ref_path) 183 | if '$ref' in parameter_obj: 184 | reference = parameter_obj['$ref'].replace(ref_path, '') 185 | type_ref = type_dict.get(reference, None) 186 | if type_ref is None: 187 | return None 188 | if 'properties' in type_ref: 189 | for property_name, property_value in six.iteritems( 190 | type_ref['properties']): 191 | prop = {'in': 'query', 'name': property_name} 192 | if 'type' in property_value: 193 | prop['type'] = property_value['type'] 194 | if prop['type'] == 'array': 195 | prop['collectionFormat'] = 'multi' 196 | prop['items'] = property_value['items'] 197 | if '$ref' in property_value['items']: 198 | ref = property_value['items']['$ref'].replace( 199 | ref_path, '') 200 | type_ref = type_dict[ref] 201 | prop['items'] = type_ref 202 | if 'description' in prop['items']: 203 | del prop['items']['description'] 204 | if 'description' in property_value: 205 | prop['description'] = property_value['description'] 206 | elif '$ref' in property_value: 207 | reference = property_value['$ref'].replace( 208 | ref_path, '') 209 | prop_obj = type_dict[reference] 210 | if 'type' in prop_obj: 211 | prop['type'] = prop_obj['type'] 212 | # Query parameter's type is object, Coverting it to 213 | # string given type object for query is not 214 | # supported by swagger 2.0. 215 | if prop['type'] == "object": 216 | prop['type'] = "string" 217 | if 'enum' in prop_obj: 218 | prop['enum'] = prop_obj['enum'] 219 | if 'description' in prop_obj: 220 | prop['description'] = prop_obj['description'] 221 | if 'required' in type_ref: 222 | if property_name in type_ref['required']: 223 | prop['required'] = True 224 | else: 225 | prop['required'] = False 226 | prop_array.append(prop) 227 | else: 228 | prop = { 229 | 'in': 'query', 230 | 'name': query_param_info.name, 231 | 'description': type_ref['description'], 232 | 'type': type_ref['type']} 233 | if 'enum' in type_ref: 234 | prop['enum'] = type_ref['enum'] 235 | if 'required' not in parameter_obj: 236 | prop['required'] = True 237 | else: 238 | prop['required'] = parameter_obj['required'] 239 | prop_array.append(prop) 240 | else: 241 | parameter_obj['in'] = 'query' 242 | parameter_obj['name'] = query_param_info.name 243 | parameter_obj['description'] = query_param_info.documentation 244 | if 'required' not in parameter_obj: 245 | parameter_obj['required'] = True 246 | prop_array.append(parameter_obj) 247 | return prop_array 248 | -------------------------------------------------------------------------------- /lib/api_endpoint/swagger2/api_swagger_response_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import requests 5 | from six.moves import http_client 6 | 7 | from lib import utils 8 | from lib.api_endpoint.api_type_handler import ApiTypeHandler 9 | 10 | 11 | class ApiSwaggerRespHandler(): 12 | 13 | def populate_response_map( 14 | self, 15 | output, 16 | errors, 17 | http_error_map, 18 | type_dict, 19 | structure_svc, 20 | enum_svc, 21 | service_id, 22 | operation_id, 23 | op_metadata, 24 | show_unreleased_apis): 25 | 26 | response_map = {} 27 | ref_path = "#/definitions/" 28 | success_response = {'description': output.documentation} 29 | schema = {} 30 | tpHandler = ApiTypeHandler(show_unreleased_apis) 31 | tpHandler.visit_type_category( 32 | output.type, 33 | schema, 34 | type_dict, 35 | structure_svc, 36 | enum_svc, 37 | ref_path) 38 | # if type of schema is void, don't include it. 39 | # this prevents showing response as void in swagger-ui 40 | if schema is not None: 41 | if not ('type' in schema and schema['type'] == 'void'): 42 | resp = schema 43 | success_response['schema'] = resp 44 | # success response is not mapped through metamodel. 45 | # hardcode it for now. 46 | success_response_code = requests.codes.ok 47 | if 'Response' in op_metadata and op_metadata['Response'] is not None: 48 | success_response_code = int(op_metadata['Response'].elements['code'].string_value) 49 | response_map[success_response_code] = success_response 50 | 51 | for error in errors: 52 | status_code = http_error_map.error_api_map.get( 53 | error.structure_id, 54 | http_client.INTERNAL_SERVER_ERROR) 55 | tpHandler.check_type( 56 | 'com.vmware.vapi.structure', 57 | error.structure_id, 58 | type_dict, 59 | structure_svc, 60 | enum_svc, 61 | ref_path) 62 | response_obj = { 63 | 'description': error.documentation, 'schema': { 64 | '$ref': ref_path + utils.get_str_camel_case( 65 | error.structure_id, *utils.CAMELCASE_SEPARATOR_LIST)}} 66 | 67 | response_map[status_code] = response_obj 68 | return response_map 69 | -------------------------------------------------------------------------------- /lib/authentication_metadata_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | 6 | # Supproted authentication metadata schemes 7 | no_authentication_scheme = 'com.vmware.vapi.std.security.no_authentication' 8 | session_id_scheme = 'com.vmware.vapi.std.security.session_id' 9 | basic_auth_scheme = 'com.vmware.vapi.std.security.user_pass' 10 | 11 | def get_authentication_dict(auth_component_svc): 12 | auth_dict = {} 13 | auth_components = auth_component_svc.list() 14 | for auth_component in auth_components: 15 | auth_component_data = auth_component_svc.get(auth_component) 16 | for package_name, package_info in six.iteritems(auth_component_data.info.packages): 17 | if package_name not in auth_dict: 18 | auth_dict[package_name] = AuthenticationComponentBuilder.build_package_level_component(package_info) 19 | else: 20 | new_package_component = AuthenticationComponentBuilder.build_package_level_component(package_info) 21 | existing_package_component = auth_dict[package_name] 22 | for service_name, service_info in six.iteritems(new_package_component.get_subcomponents_dict()): 23 | existing_package_component.add_subcomponent(service_info, service_name) 24 | return auth_dict 25 | 26 | 27 | class AuthenticationDictNavigator: 28 | 29 | def __init__(self, auth_dict): 30 | self.auth_dict = auth_dict 31 | 32 | def find_schemes_set(self, operation_id, service_name, package): 33 | component = self.__preorder_depth_first_search_component(service_name, package) 34 | if component is not None: 35 | operation_component = component.get_subcomponents_dict().get(operation_id) 36 | if operation_component is not None: 37 | return operation_component.get_schemes_set() 38 | else: 39 | return component.get_schemes_set() 40 | 41 | service_name = ".".join(service_name.split('.')[:-1]) 42 | while service_name: 43 | component = self.__preorder_depth_first_search_component(service_name, package) 44 | if component is not None: 45 | return component.get_schemes_set() 46 | else: 47 | service_name = ".".join(service_name.split('.')[:-1]) 48 | 49 | return None 50 | 51 | 52 | def __preorder_depth_first_search_component(self, service_name, package): 53 | if service_name in self.auth_dict: 54 | return self.auth_dict[service_name] 55 | else: 56 | for package_name, package_component in six.iteritems(self.auth_dict): 57 | if package not in package_name: 58 | continue 59 | 60 | component = package_component.recursive_search_for_component(service_name) 61 | if component is not None: 62 | return component 63 | 64 | return None 65 | 66 | 67 | class AuthenticationComponentBuilder: 68 | 69 | @staticmethod 70 | def build_package_level_component(package_info): 71 | package_component = AuthenticationComponent() 72 | package_component.add_schemes(AuthenticationComponentBuilder.__extract_schemes_list(package_info)) 73 | for service_name, service_info in six.iteritems(package_info.services): 74 | service_component = AuthenticationComponentBuilder.build_service_level_component(service_info) 75 | package_component.add_subcomponent(service_component, service_name) 76 | return package_component 77 | 78 | @staticmethod 79 | def build_service_level_component(service_info): 80 | service_component = AuthenticationComponent() 81 | service_component.add_schemes(AuthenticationComponentBuilder.__extract_schemes_list(service_info)) 82 | for operation_name, operation_info in six.iteritems(service_info.operations): 83 | operation_component = AuthenticationComponentBuilder.build_operation_level_component(operation_info) 84 | service_component.add_subcomponent(operation_component, operation_name) 85 | return service_component 86 | 87 | @staticmethod 88 | def build_operation_level_component(operation_info): 89 | operation_component = AuthenticationComponent() 90 | operation_component.add_schemes(AuthenticationComponentBuilder.__extract_schemes_list(operation_info)) 91 | return operation_component 92 | 93 | @staticmethod 94 | def __extract_schemes_list(auth_component): 95 | return [auth_info.scheme for auth_info in auth_component.schemes] 96 | 97 | 98 | class AuthenticationComponent: 99 | 100 | def __init__(self): 101 | self.scheme_set = set() 102 | self.subcomponents_dict = {} 103 | 104 | def add_schemes(self, schemes_iterable): 105 | self.scheme_set.update(schemes_iterable) 106 | 107 | def get_schemes_set(self): 108 | return self.scheme_set 109 | 110 | def get_subcomponents_dict(self): 111 | return self.subcomponents_dict 112 | 113 | def add_subcomponent(self, added_auth_component, auth_component_name): 114 | if auth_component_name not in self.subcomponents_dict: 115 | self.subcomponents_dict[auth_component_name] = added_auth_component 116 | else: 117 | existing_component = self.subcomponents_dict[auth_component_name] 118 | existing_component.add_schemes(added_auth_component.get_schemes_set()) 119 | for name_added_subcomponent, added_subcomponent in six.iteritems(added_auth_component.get_subcomponents_dict()): 120 | existing_component.add_subcomponent(added_subcomponent, name_added_subcomponent) 121 | 122 | def recursive_search_for_component(self, component_name): 123 | if self.subcomponents_dict == {}: 124 | return None 125 | elif component_name in self.subcomponents_dict: 126 | return self.subcomponents_dict[component_name] 127 | else: 128 | for _, subcomponent in six.iteritems(self.subcomponents_dict): 129 | return subcomponent.recursive_search_for_component(component_name) 130 | -------------------------------------------------------------------------------- /lib/blacklist_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | blacklist = {"rest": ['com.vmware.vcenter.vm.compute.policies', 5 | 'com.vmware.vcenter.compute.policies.tag_usage', 6 | 'com.vmware.vcenter.compute.policies.VM', 7 | 'com.vmware.vcenter.compute.policies.capabilities', 8 | 'com.vmware.vcenter.compute.policies'], 9 | "api": []} 10 | 11 | 12 | def is_blacklisted_for_rest(service): 13 | return service in blacklist["rest"] 14 | 15 | 16 | def is_blacklisted_for_api(service): 17 | return service in blacklist["api"] 18 | -------------------------------------------------------------------------------- /lib/dictionary_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | 6 | from lib import utils 7 | from lib import blacklist_utils 8 | 9 | 10 | class ServiceType: 11 | SLASH_REST = 1 12 | SLASH_API = 2 13 | SLASH_REST_AND_API = 3 14 | 15 | def populate_dicts( 16 | component_svc, 17 | enumeration_dict, 18 | structure_dict, 19 | service_dict, 20 | service_urls_map, 21 | base_url, 22 | generate_metamodel): 23 | components = component_svc.list() 24 | for component in components: 25 | component_data = component_svc.get(component) 26 | if generate_metamodel: 27 | if not os.path.exists('metamodel'): 28 | os.mkdir('metamodel') 29 | utils.write_json_data_to_file( 30 | 'metamodel/' + component + '.json', 31 | objectTodict(component_data)) 32 | component_packages = component_data.info.packages 33 | for package in component_packages: 34 | package_info = component_packages.get(package) 35 | for enumeration, enumeration_info in package_info.enumerations.items(): 36 | enumeration_dict[enumeration] = enumeration_info 37 | for structure, structure_info in package_info.structures.items(): 38 | structure_dict[structure] = structure_info 39 | for enum_name, enum_info in structure_info.enumerations.items(): 40 | enumeration_dict[enum_name] = enum_info 41 | for service, service_info in package_info.services.items(): 42 | service_dict[service] = service_info 43 | service_urls_map[get_service_url_from_service_id( 44 | base_url, service)] = service 45 | for structure_name, structure_info in service_info.structures.items(): 46 | structure_dict[structure_name] = structure_info 47 | for et1, et_info1 in structure_info.enumerations.items(): 48 | enumeration_dict[et1] = et_info1 49 | for enum_name, enum_info in service_info.enumerations.items(): 50 | enumeration_dict[enum_name] = enum_info 51 | 52 | 53 | def objectTodict(obj): 54 | objtype = type(obj) 55 | if objtype is int or objtype is str or objtype is float or isinstance( 56 | None, objtype) or objtype is bool: 57 | pass 58 | elif objtype is dict: 59 | temp = {} 60 | for key, value in obj.items(): 61 | temp[key] = objectTodict(value) 62 | 63 | obj = temp 64 | elif objtype is list: 65 | temp = [] 66 | for value in obj: 67 | temp.append(objectTodict(value)) 68 | obj = temp 69 | else: 70 | if obj.__dict__ != {}: 71 | obj = objectTodict(obj.__dict__) 72 | 73 | return obj 74 | 75 | 76 | def get_service_url_from_service_id(base_url, service_id): 77 | replaced_string = service_id.replace('.', '/') 78 | return base_url + '/' + replaced_string.replace('_', '-') 79 | 80 | 81 | def get_service_urls_from_rest_navigation(rest_navigation_url, verify): 82 | component_services_urls = get_component_services_urls( 83 | rest_navigation_url, verify) 84 | return get_all_services_urls(component_services_urls, verify) 85 | 86 | 87 | def get_component_services_urls(cloudvm_url, verify): 88 | components_url = utils.get_json(cloudvm_url, verify)['components']['href'] 89 | components = utils.get_json(components_url, verify) 90 | return [component['services']['href'] for component in components] 91 | 92 | 93 | def get_all_services_urls(components_urls, verify): 94 | service_url_dict = {} 95 | for url in components_urls: 96 | services = utils.get_json(url, verify) 97 | for service in services: 98 | service_url_dict[service['href']] = service['name'] 99 | return service_url_dict 100 | 101 | 102 | def add_service_urls_using_metamodel( 103 | service_urls_map, 104 | service_dict, 105 | rest_navigation_handler, 106 | auto_rest_services, 107 | deprecate_rest=False): 108 | 109 | package_dict_api = {} 110 | package_dict = {} 111 | package_dict_deprecated = {} 112 | ''' 113 | The replacement navigation map is used when DEPRECATED specification is issued (@VERB + an old annotation standard) 114 | It contains mappings to url paths, served as replacements. The structure of the map is the following: 115 | service -> operation -> method -> raplacement path 116 | ''' 117 | replacement_dict = {} 118 | 119 | rest_services = {} 120 | for k, v in service_urls_map.items(): 121 | rest_services.update({ 122 | v: k 123 | }) 124 | 125 | for service in service_dict: 126 | service_type, path_list = get_paths_inside_metamodel(service, 127 | service_dict, 128 | auto_rest_services, 129 | deprecate_rest, 130 | replacement_dict, 131 | rest_services.get(service, None), 132 | rest_navigation_handler) 133 | if (service_type in [ServiceType.SLASH_API, ServiceType.SLASH_REST_AND_API]) and not blacklist_utils.is_blacklisted_for_api(service): 134 | for path in path_list: 135 | service_urls_map[path] = (service, '/api') 136 | package_name = path.split('/')[1] 137 | pack_arr = package_dict_api.get(package_name, []) 138 | if pack_arr == []: 139 | package_dict_api[package_name] = pack_arr 140 | pack_arr.append(path) 141 | elif service_type == ServiceType.SLASH_REST and not blacklist_utils.is_blacklisted_for_rest(service): 142 | service_url = rest_services.get(service, None) 143 | if service_url is not None: 144 | service_path = get_service_path_from_service_url( 145 | service_url, rest_navigation_handler.get_rest_navigation_url()) 146 | service_urls_map[service_path] = (service, '/rest') 147 | package = service_path.split('/')[3] 148 | if package in package_dict: 149 | packages = package_dict[package] 150 | packages.append(service_path) 151 | else: 152 | package_dict.setdefault(package, [service_path]) 153 | else: 154 | print("Service does not belong to either /api or /rest ", service) 155 | if service_type == ServiceType.SLASH_REST_AND_API and not blacklist_utils.is_blacklisted_for_rest(service): 156 | service_url = rest_services.get(service, None) 157 | if service_url is not None: 158 | service_path = get_service_path_from_service_url( 159 | service_url, rest_navigation_handler.get_rest_navigation_url()) 160 | service_urls_map[service_path] = (service, '/deprecated') 161 | package = service_path.split('/')[3] 162 | if package in package_dict_deprecated: 163 | packages = package_dict_deprecated[package] 164 | packages.append(service_path) 165 | else: 166 | package_dict_deprecated.setdefault(package, [service_path]) 167 | else: 168 | print("Service does not belong to either /api or /rest ", service) 169 | 170 | return package_dict_api, package_dict, package_dict_deprecated, replacement_dict 171 | 172 | 173 | #TODO the overly complicated method below along with add_service_urls_using_metamodel should be refactored 174 | # They should be separated in different strategies, for each api type - /rest, /api and deprecated (/rest and /api) 175 | def get_paths_inside_metamodel(service, 176 | service_dict, 177 | auto_rest_services, 178 | deprecate_rest=False, 179 | replacement_dict={}, 180 | service_url=None, 181 | rest_navigation_handler=None): 182 | path_list = set() 183 | has_rest_counterpart = False 184 | is_in_rest_navigation = False 185 | is_rest_navigation_checked = False 186 | 187 | for operation_id in service_dict[service].operations.keys(): 188 | for request in service_dict[service].operations[operation_id].metadata.keys( 189 | ): 190 | # Is a 7.0 VERB /api check 191 | if request.lower() in ('post', 'put', 'patch', 'get', 'delete'): 192 | if 'path' not in service_dict[service].operations[operation_id].metadata[request].elements: 193 | continue 194 | 195 | path = service_dict[service].operations[operation_id].metadata[request].elements['path'].string_value 196 | 197 | # If already found in navigation, no need to check for request mapping 198 | if not is_in_rest_navigation: 199 | has_rest_counterpart = check_for_request_mapping_replacement(service_dict[service], operation_id) 200 | 201 | # Check rest navigation if no rest counterpart has been already found and no prior call has been made 202 | if not has_rest_counterpart and not is_rest_navigation_checked: 203 | if auto_rest_services: 204 | has_rest_counterpart = service in auto_rest_services 205 | else: 206 | has_rest_counterpart = check_for_rest_navigation_replacement(service_url, rest_navigation_handler) 207 | # Add all operations and methods to replacements if it is apparent in rest_navigation 208 | is_in_rest_navigation = has_rest_counterpart 209 | is_rest_navigation_checked = True 210 | 211 | if has_rest_counterpart: 212 | url = path 213 | if 'params' in service_dict[service].operations[operation_id].metadata[request].elements: 214 | element_value = service_dict[service].operations[operation_id].metadata[request].elements['params'] 215 | params = "&".join(element_value.list_value) 216 | url = path + '?' + params 217 | add_replacement_path(service, operation_id, request.lower(), url, replacement_dict) 218 | 219 | if not has_rest_counterpart or deprecate_rest or blacklist_utils.is_blacklisted_for_rest(service): 220 | path_list.add(path) 221 | 222 | if path_list == set(): 223 | return ServiceType.SLASH_REST, [] 224 | 225 | if has_rest_counterpart: 226 | return ServiceType.SLASH_REST_AND_API, sorted(list(path_list)) 227 | 228 | return ServiceType.SLASH_API, sorted(list(path_list)) 229 | 230 | 231 | def check_for_request_mapping_replacement(service, operation_id): 232 | # Check whether the service contains both @RequestMapping and @Verb annotations 233 | if 'RequestMapping' in service.operations[operation_id].metadata.keys(): 234 | return True 235 | return False 236 | 237 | def check_for_rest_navigation_replacement(service_url, rest_navigation_handler): 238 | if service_url is not None and rest_navigation_handler is not None: 239 | # Check whether the service is apparent in the rest navigation - has 6.0 240 | service_operations = rest_navigation_handler.get_service_operations(service_url) 241 | if service_operations is not None: 242 | return True 243 | return False 244 | 245 | 246 | def get_service_path_from_service_url(service_url, base_url): 247 | if not service_url.startswith(base_url): 248 | return service_url 249 | 250 | return service_url[len(base_url):] 251 | 252 | 253 | def add_replacement_path(service, operation_id, method, path, replacement_dict): 254 | if service not in replacement_dict: 255 | replacement_dict[service] = {operation_id: (method, path)} 256 | else: 257 | replacement_dict[service][operation_id] = (method, path) 258 | -------------------------------------------------------------------------------- /lib/establish_connection.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import argparse 5 | import os 6 | from vmware.vapi.stdlib.client.factories import StubConfigurationFactory 7 | from com.vmware.vapi.metadata import metamodel_client 8 | from com.vmware.vapi.metadata import authentication_client 9 | 10 | 11 | def get_input_params(): 12 | """ 13 | Gets input parameters from command line 14 | :return: 15 | """ 16 | parser = argparse.ArgumentParser( 17 | description='Generate swagger.json files for apis on vcenter') 18 | parser.add_argument('-m', '--metadata-url', help='URL of the metadata API') 19 | parser.add_argument( 20 | '-rn', 21 | '--rest-navigation-url', 22 | help='URL of the rest-navigation API') 23 | parser.add_argument( 24 | '-vc', 25 | '--vcip', 26 | help='IP Address of vCenter Server. If specified, would be used' 27 | ' to calculate metadata-url and rest-navigation-url') 28 | parser.add_argument( 29 | '-o', '--output', help='Output directory of swagger files. if not specified,' 30 | ' current working directory is chosen as output directory') 31 | parser.add_argument( 32 | '-s', 33 | '--tag-separator', 34 | default='/', 35 | help='Separator to use in tag name') 36 | parser.add_argument( 37 | '-k', 38 | '--insecure', 39 | action='store_true', 40 | help='Bypass SSL certificate validation') 41 | parser.add_argument( 42 | "-uo", 43 | "--unique-operation-ids", 44 | required=False, 45 | nargs='?', 46 | const=True, 47 | default=False, 48 | help="Pass this parameter to generate Unique Operation Ids.") 49 | parser.add_argument( 50 | "-c", 51 | "--metamodel-components", 52 | required=False, 53 | nargs='?', 54 | const=True, 55 | default=False, 56 | help="Pass this parameter to save each metamodel component as a new json file") 57 | parser.add_argument( 58 | '--host', 59 | help='Domain name or IP address (IPv4) of the host that serves the API. ' 60 | 'Default value is ""') 61 | parser.add_argument( 62 | '-su', 63 | '--show-unreleased', 64 | required=False, 65 | nargs='?', 66 | const=True, 67 | default=False, 68 | dest='show_unreleased_apis', 69 | help='Includes internal and unreleased apis') 70 | parser.add_argument( 71 | '-oas', 72 | '--oas', 73 | default='3', 74 | help='opeanpi specification version') 75 | parser.add_argument( 76 | '-dsr', 77 | '--deprecate-slash-rest', 78 | required=False, 79 | nargs='?', 80 | const=True, 81 | default=False, 82 | dest='deprecate_rest', 83 | help='/api and /rest rendering - with /rest being deprecated') 84 | parser.add_argument( 85 | '-fam', 86 | '--fetch-authentication-metadata', 87 | required=False, 88 | nargs='?', 89 | const=True, 90 | default=False, 91 | dest='fetch_auth_metadata', 92 | help='retrieves authentication information from the authentication metadata service') 93 | parser.add_argument( 94 | '-ars', 95 | '--auto-rest-services', 96 | nargs='+', 97 | default=[], 98 | dest='auto_rest_services', 99 | help='list of services described with auto rest') 100 | args = parser.parse_args() 101 | metadata_url = args.metadata_url 102 | rest_navigation_url = args.rest_navigation_url 103 | vcip = args.vcip 104 | if vcip is not None: 105 | if metadata_url is None: 106 | metadata_url = 'https://%s/api' % vcip 107 | if rest_navigation_url is None: 108 | rest_navigation_url = 'https://%s/rest' % vcip 109 | 110 | if metadata_url is None or rest_navigation_url is None: 111 | raise ValueError( 112 | 'metadataUrl and restNavigationUrl are required parameters') 113 | metadata_url = metadata_url.rstrip('/') 114 | rest_navigation_url = rest_navigation_url.rstrip('/') 115 | output_dir = args.output 116 | if args.host is not None: 117 | global API_SERVER_HOST 118 | API_SERVER_HOST = args.host 119 | if output_dir is None: 120 | output_dir = os.getcwd() 121 | verify = not args.insecure 122 | 123 | global GENERATE_UNIQUE_OP_IDS 124 | GENERATE_UNIQUE_OP_IDS = args.unique_operation_ids 125 | 126 | global TAG_SEPARATOR 127 | TAG_SEPARATOR = args.tag_separator 128 | 129 | global show_unreleased_apis 130 | show_unreleased_apis = args.show_unreleased_apis 131 | 132 | global GENERATE_METAMODEL 133 | GENERATE_METAMODEL = args.metamodel_components 134 | 135 | global SPECIFICATION 136 | if args.oas not in ['2', '3']: 137 | raise Exception(" Input Valid Specification ") 138 | SPECIFICATION = args.oas 139 | 140 | global DEPRECATE_REST 141 | DEPRECATE_REST = args.deprecate_rest 142 | 143 | fetch_auth_metadata = args.fetch_auth_metadata 144 | 145 | auto_rest_services = args.auto_rest_services 146 | 147 | return metadata_url,\ 148 | rest_navigation_url,\ 149 | output_dir,\ 150 | verify,\ 151 | show_unreleased_apis,\ 152 | GENERATE_METAMODEL,\ 153 | SPECIFICATION,\ 154 | GENERATE_UNIQUE_OP_IDS,\ 155 | TAG_SEPARATOR,\ 156 | DEPRECATE_REST,\ 157 | fetch_auth_metadata,\ 158 | auto_rest_services 159 | 160 | 161 | def get_component_service(connector): 162 | stub_config = StubConfigurationFactory.new_std_configuration(connector) 163 | component_svc = metamodel_client.Component(stub_config) 164 | return component_svc 165 | 166 | def get_authentication_component_service(connector): 167 | stub_config = StubConfigurationFactory.new_std_configuration(connector) 168 | component_svc = authentication_client.Component(stub_config) 169 | return component_svc -------------------------------------------------------------------------------- /lib/file_output_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | 6 | import six 7 | 8 | from lib import utils 9 | from lib.openapi_final_path_processing import OpenapiPathProcessing 10 | from lib.swagger_final_path_processing import SwaggerPathProcessing 11 | 12 | 13 | class FileOutputHandler: 14 | 15 | def __init__(self, 16 | rest_package_spec_dict, 17 | api_package_spec_dict, 18 | output_dir, 19 | gen_unique_op_id, 20 | spec, 21 | split_api_rest=False): 22 | self.rest_package_spec_dict = rest_package_spec_dict 23 | self.api_package_spec_dict = api_package_spec_dict 24 | self.output_dir = output_dir 25 | self.gen_unique_op_id = gen_unique_op_id 26 | self.split_api_rest = split_api_rest 27 | 28 | if spec == '2': 29 | self.processor = SwaggerPathProcessing() 30 | else: 31 | self.processor = OpenapiPathProcessing() 32 | 33 | for package, path_type_tuple in six.iteritems(self.rest_package_spec_dict): 34 | self.__preprocess_dict(path_type_tuple[0], path_type_tuple[1]) 35 | for package, path_type_tuple in six.iteritems(self.api_package_spec_dict): 36 | self.__preprocess_dict(path_type_tuple[0], path_type_tuple[1], True) 37 | 38 | def __preprocess_dict(self, path_dict, type_dict, add_camel_case=False): 39 | processor = self.processor 40 | processor.remove_com_vmware_from_dict(path_dict, 0, [], add_camel_case) 41 | if self.gen_unique_op_id: 42 | processor.create_unique_op_ids(path_dict) 43 | #processor.remove_query_params(path_dict) 44 | processor.remove_com_vmware_from_dict(type_dict, 0, [], add_camel_case) 45 | 46 | def __output_spec(self, package_name, path_dict, type_dict, file_prefix=''): 47 | self.processor.process_output( 48 | path_dict, 49 | type_dict, 50 | self.output_dir, 51 | package_name, 52 | self.gen_unique_op_id, 53 | file_prefix) 54 | 55 | def __produce_merged(self): 56 | merger = SpecificationDictsMerger(self.rest_package_spec_dict.copy(), 57 | self.api_package_spec_dict.copy()) 58 | merged_dict = merger.merge_api_rest_dicts() 59 | for package, path_type_tuple in six.iteritems(merged_dict): 60 | self.__output_spec(package, path_type_tuple[0], path_type_tuple[1]) 61 | 62 | def __produce_split(self): 63 | for package, path_type_tuple in six.iteritems(self.rest_package_spec_dict): 64 | self.__output_spec(package, path_type_tuple[0], path_type_tuple[1], "rest") 65 | for package, path_type_tuple in six.iteritems(self.api_package_spec_dict): 66 | self.__output_spec(package, path_type_tuple[0], path_type_tuple[1], "api") 67 | 68 | def __produce_api_json(self): 69 | # api.json contains list of packages which is used by UI to dynamically 70 | # populate dropdown. 71 | api_files_list = [] 72 | for name in list(self.rest_package_spec_dict.keys()): 73 | if self.split_api_rest: 74 | api_files_list.append("rest_" + name) 75 | else: 76 | api_files_list.append(name) 77 | for name in list(self.api_package_spec_dict.keys()): 78 | if self.split_api_rest: 79 | api_files_list.append("api_" + name) 80 | else: 81 | api_files_list.append(name) 82 | api_files_list = list(set(api_files_list)) 83 | 84 | api_files = {'files': api_files_list} 85 | utils.write_json_data_to_file( 86 | self.output_dir + 87 | os.path.sep + 88 | 'api.json', 89 | api_files) 90 | 91 | def output_files(self): 92 | if self.split_api_rest: 93 | self.__produce_split() 94 | else: 95 | self.__produce_merged() 96 | self.__produce_api_json() 97 | 98 | 99 | class SpecificationDictsMerger: 100 | 101 | def __init__(self, 102 | rest_package_spec_dict, 103 | api_package_spec_dict): 104 | self.rest_package_spec_dict = rest_package_spec_dict 105 | self.api_package_spec_dict = api_package_spec_dict 106 | 107 | def merge_api_rest_dicts(self): 108 | for package, path_type_tuple in six.iteritems(self.api_package_spec_dict): 109 | if package in self.rest_package_spec_dict: 110 | # Transitive dependency between function calls 111 | self.__merge_type_dicts(self.rest_package_spec_dict[package][1], path_type_tuple[1]) 112 | self.__merge_path_dicts(self.rest_package_spec_dict[package][0], path_type_tuple[0]) 113 | else: 114 | self.rest_package_spec_dict[package] = path_type_tuple 115 | return self.rest_package_spec_dict 116 | 117 | def __merge_path_dicts(self, path_dict_extended, path_dict_added): 118 | # Since paths begin either with /api or /rest, no collisions are expected 119 | path_dict_extended.update(path_dict_added) 120 | 121 | def __merge_type_dicts(self, type_dict_extended, type_dict_added): 122 | # Since /api definitions are CamelCased, no collisions are expected 123 | type_dict_extended.update(type_dict_added) 124 | 125 | def __update_rest_references(self, new_ref, old_ref, package): 126 | path_dict, type_dict = self.rest_package_spec_dict[package] 127 | utils.recursive_ref_update(path_dict, old_ref, new_ref) 128 | utils.recursive_ref_update(type_dict, old_ref, new_ref) 129 | -------------------------------------------------------------------------------- /lib/metadata_processor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | 6 | class MetadataProcessor(): 7 | 8 | def __init__(self): 9 | pass 10 | 11 | def find_url(self, list_of_links): 12 | """ 13 | There are many apis which get same work done. 14 | The idea here is to show the best one. 15 | Here is the logic for picking the best one. 16 | * if there is only one element in the list, the choice is obvious. 17 | * if there are more than one: 18 | return for a link which does not contain "~action" in them and which contain "id:{}" in them. 19 | """ 20 | if len(list_of_links) == 1: 21 | return list_of_links[0]['href'], list_of_links[0]['method'] 22 | 23 | non_action_link = None 24 | for link in list_of_links: 25 | if '~action=' not in link['href']: 26 | if "id:" in link['href']: 27 | return link['href'], link['method'] 28 | if non_action_link is None: 29 | non_action_link = link 30 | if non_action_link is None: 31 | # all links have ~action in them. check if any of them has id: and 32 | # return it. 33 | for link in list_of_links: 34 | if "id:" in link['href']: 35 | return link['href'], link['method'] 36 | 37 | # all links have ~action in them and none of them have id: (pick 38 | # any one) 39 | return list_of_links[0]['href'], list_of_links[0]['method'] 40 | 41 | return non_action_link['href'], non_action_link['method'] 42 | 43 | def get_service_path_from_service_url(self, service_url, base_url): 44 | if not service_url.startswith(base_url): 45 | return "/rest" + service_url 46 | return "/rest" + service_url[len(base_url):] 47 | 48 | def convert_path_list_to_path_map(self, path_list): 49 | """ 50 | The same path can have multiple methods. 51 | For example: /vcenter/vm can have 'get', 'patch', 'put' 52 | Rearrange list into a map/object which is the format expected by swagger-ui 53 | key is the path ie. /vcenter/vm/ 54 | value is a an object which contains key as method names and value as path objects 55 | """ 56 | path_dict = {} 57 | for path in path_list: 58 | x = path_dict.get(path['path']) 59 | if x is None: 60 | x = {path['method']: path} 61 | path_dict[path['path']] = x 62 | else: 63 | x[path['method']] = path 64 | return path_dict 65 | 66 | def cleanup(self, path_dict, type_dict): 67 | for _, type_object in six.iteritems(type_dict): 68 | if 'properties' in type_object or 'additionalProperties' in type_object: 69 | 70 | if 'properties' in type_object: 71 | properties = type_object['properties'] 72 | else: 73 | properties = type_object['additionalProperties'] 74 | 75 | for key, property_value in properties.items(): 76 | if isinstance(property_value, dict): 77 | if 'required' in property_value and isinstance( 78 | property_value['required'], bool): 79 | del property_value['required'] 80 | 81 | for _, path_value in six.iteritems(path_dict): 82 | for _, method_value in six.iteritems(path_value): 83 | if 'path' in method_value: 84 | del method_value['path'] 85 | if 'method' in method_value: 86 | del method_value['method'] 87 | -------------------------------------------------------------------------------- /lib/openapi_final_path_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | import re 6 | import collections 7 | from lib import utils 8 | from lib.path_processing import PathProcessing 9 | 10 | 11 | class OpenapiPathProcessing(PathProcessing): 12 | 13 | def __init__(self): 14 | pass 15 | 16 | def process_output( 17 | self, 18 | path_dict, 19 | type_dict, 20 | output_dir, 21 | output_filename, 22 | gen_unique_op_id, 23 | prefix=''): 24 | reqBody = {} 25 | description_map = utils.load_description() 26 | if 'requestBodies' in type_dict: 27 | self.remove_com_vmware_from_dict(type_dict['requestBodies']) 28 | reqBody = collections.OrderedDict( 29 | sorted(type_dict['requestBodies'].items())) 30 | 31 | file_prefix = '' 32 | if prefix != '': 33 | file_prefix = prefix + "_" 34 | 35 | swagger_template = { 36 | 'openapi': '3.0.0', 37 | 'info': { 38 | 'description': description_map.get(output_filename, ''), 39 | 'title': utils.remove_curly_braces(output_filename), 40 | 'version': '2.0.0'}, 41 | 'paths': collections.OrderedDict(sorted(path_dict.items())), 42 | 'components': { 43 | 'requestBodies': reqBody}} 44 | if 'requestBodies' in type_dict: 45 | del type_dict['requestBodies'] 46 | swagger_template['components']['schemas'] = collections.OrderedDict( 47 | sorted(type_dict.items())) 48 | 49 | if not os.path.exists(output_dir): 50 | os.mkdir(output_dir) 51 | 52 | utils.write_json_data_to_file( 53 | output_dir + 54 | os.path.sep + 55 | file_prefix + 56 | utils.remove_curly_braces(output_filename) + 57 | '.json', 58 | swagger_template) 59 | 60 | def remove_query_params(self, path_dict): 61 | """ 62 | Swagger/Open API specification prohibits appending query parameter to the request mapping path. 63 | 64 | Duplicate paths in Open API : 65 | Since request mapping paths are keys in the Open Api JSON, there is no scope of duplicate request mapping paths 66 | 67 | Partial Duplicates in Open API: APIs which have same request mapping paths but different HTTP Operations. 68 | 69 | Such Operations can be merged together under one path 70 | eg: Consider these two paths 71 | /A/B/C : [POST] 72 | /A/B/C : [PUT] 73 | On merging these, the new path would look like: 74 | /A/B/C : [POST, PUT] 75 | 76 | Absolute Duplicates in Open API: APIs which have same request mapping path and HTTP Operation(s) 77 | eg: Consider two paths 78 | /A/B/C : [POST, PUT] 79 | /A/B/C : [PUT] 80 | Such paths can not co-exist in the same Open API definition. 81 | 82 | This method attempts to move query parameters from request mapping url to parameter section. 83 | 84 | There are 4 possibilities which may arise on removing the query parameter from request mapping path: 85 | 86 | 1. Absolute Duplicate 87 | The combination of path and the HTTP Operation Type(s)are same to that of an existing path: 88 | Handling Such APIs is Out of Scope of this method. Such APIs will appear in the Open API definition unchanged. 89 | Example : 90 | /com/vmware/cis/session?~action=get : [POST] 91 | /com/vmware/cis/session : [POST, DELETE] 92 | 2. Partial Duplicate: 93 | The Paths are same but the HTTP operations are Unique: 94 | Handling Such APIs involves adding the Operations of the new duplicate path to that of the existing path 95 | Example : 96 | /cis/tasks/{task}?action=cancel : [POST] 97 | /cis/tasks/{task} : [GET] 98 | 3. New Unique Path: 99 | The new path is not a duplicate of any path in the current Open API definition. 100 | The Path is changed to new path by trimming off the path post '?' 101 | 102 | 4. The duplicate paths are formed when two paths with QueryParameters are fixed 103 | All the scenarios under 1, 2 and 3 are possible. 104 | Example : 105 | /com/vmware/cis/tagging/tag-association/id:{tag_id}?~action=detach-tag-from-multiple-objects 106 | /com/vmware/cis/tagging/tag-association/id:{tag_id}?~action=list-attached-objects 107 | :param path_dict: 108 | """ 109 | paths_to_delete = [] 110 | for old_path in list(path_dict.keys()): 111 | http_operations = path_dict[old_path] 112 | if '?' in old_path: 113 | paths_array = re.split(r'\?', old_path) 114 | new_path = paths_array[0] 115 | 116 | query_param = [] 117 | for query_parameter in paths_array[1].split('&'): 118 | key_value = query_parameter.split('=') 119 | q_param = {'name': key_value[0], 120 | 'in': 'query', 121 | 'description': key_value[0], 122 | 'required': True, 123 | 'schema': {'type': 'string'} 124 | } 125 | if len(key_value) == 2: 126 | q_param['description'] = key_value[0] + '=' + key_value[1] 127 | q_param['schema']['enum'] = [key_value[1]] 128 | query_param.append(q_param) 129 | 130 | if new_path in path_dict: 131 | new_path_operations = path_dict[new_path].keys() 132 | path_operations = http_operations.keys() 133 | if len(set(path_operations).intersection( 134 | new_path_operations)) < 1: 135 | for http_method, operation_dict in http_operations.items(): 136 | operation_dict['parameters'] = operation_dict['parameters'] + query_param 137 | path_dict[new_path] = self.merge_dictionaries( 138 | http_operations, path_dict[new_path]) 139 | paths_to_delete.append(old_path) 140 | else: 141 | for http_method, operation_dict in http_operations.items(): 142 | operation_dict['parameters'].append(q_param) 143 | path_dict[new_path] = path_dict.pop(old_path) 144 | for path in paths_to_delete: 145 | del path_dict[path] 146 | -------------------------------------------------------------------------------- /lib/path_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import re 5 | 6 | from lib import utils 7 | 8 | 9 | class PathProcessing(): 10 | def __init__(self): 11 | pass 12 | 13 | def remove_com_vmware_from_dict(self, swagger_obj, depth=0, keys_list=[], add_camel_case=False): 14 | """ 15 | The method 16 | 1. removes 'com.vmware.' from model names 17 | 2. replaces $ with _ from the model names 18 | 19 | This is done on both definitions and path 20 | 'definitions' : where models are defined and may be referenced. 21 | 'path' : where models are referenced. 22 | :param swagger_obj: should be path of definitions dictionary 23 | :param depth: depth of the dictionary. Defaults to 0 24 | :param keys_list: List of updated model names 25 | :return: 26 | """ 27 | if isinstance(swagger_obj, dict): 28 | if '$ref' in swagger_obj and 'required' in swagger_obj: 29 | del swagger_obj['required'] 30 | for key, item in swagger_obj.items(): 31 | if isinstance(item, str): 32 | if key in ('$ref', 'summary', 'description'): 33 | item = item.replace('com.vmware.', '').replace('ComVmware', '') 34 | if key == '$ref': 35 | item = item.replace('$', '_') 36 | if add_camel_case: 37 | item = utils.get_str_camel_case(item, "_") 38 | swagger_obj[key] = item 39 | elif isinstance(item, list): 40 | for itm in item: 41 | self.remove_com_vmware_from_dict( 42 | itm, depth + 1, keys_list, add_camel_case) 43 | elif isinstance(item, dict): 44 | if depth == 0 and isinstance(key, str) and ( 45 | key.startswith('com.vmware.') or 46 | key.startswith('ComVmware') or '$' in key): 47 | keys_list.append(key) 48 | self.remove_com_vmware_from_dict( 49 | item, depth + 1, keys_list, add_camel_case) 50 | elif isinstance(swagger_obj, list): 51 | for itm in swagger_obj: 52 | self.remove_com_vmware_from_dict(itm, depth + 1, add_camel_case) 53 | if depth == 0 and len(keys_list) > 0: 54 | while keys_list: 55 | old_key = keys_list.pop() 56 | new_key = old_key.replace('com.vmware.', '').replace('ComVmware', '') 57 | new_key = new_key.replace('$', '_') 58 | if add_camel_case: 59 | new_key = utils.get_str_camel_case(new_key, "_") 60 | try: 61 | swagger_obj[new_key] = swagger_obj.pop(old_key) 62 | except KeyError: 63 | print( 64 | 'Could not find the Swagger Element : {}'.format(old_key)) 65 | 66 | def create_unique_op_ids(self, path_dict): 67 | """ 68 | Creates unique operation ids 69 | Takes the path dictionary as input parameter: 70 | 1. Iterates through all the http_operation array 71 | 2. For every operation gets the current operation id 72 | 3. Calls method to get the camelized operation id 73 | 4. Checks for uniqueness 74 | 5. Updates the path dictionary with the unique operation id 75 | 76 | :param path_dict: 77 | """ 78 | op_id_list = [ 79 | 'get', 80 | 'set', 81 | 'list', 82 | 'add', 83 | 'run', 84 | 'start', 85 | 'stop', 86 | 'restart', 87 | 'reset', 88 | 'cancel', 89 | 'create', 90 | 'update', 91 | 'delete'] 92 | for path, http_operation in path_dict.items(): 93 | for http_method, operation_dict in http_operation.items(): 94 | op_id_val = self.create_camelized_op_id( 95 | path, http_method, operation_dict) 96 | if op_id_val not in op_id_list: 97 | operation_dict['operationId'] = op_id_val 98 | op_id_list.append(op_id_val) 99 | 100 | def create_camelized_op_id(self, path, http_method, operations_dict): 101 | """ 102 | Creates camelized operation id. 103 | Takes the path, http_method and operation dictionary as input parameter: 104 | 1. Iterates through all the operation array to return the current operation id 105 | 2. Appends path to the existing operation id and 106 | replaces '/' and '-' with '_' and removes 'com_vmware_' 107 | 3. Splits the string by '_' 108 | 4. Converts the first letter of all the words except the first one from lower to upper 109 | 5. Joins all the words together and returns the new camelcase string 110 | 111 | e.g 112 | parameter : abc_def_ghi 113 | return : AbcDefGhi 114 | :param path: 115 | :param http_method: 116 | :param operations_dict: 117 | :return: new_op_id 118 | """ 119 | curr_op_id = operations_dict['operationId'] 120 | raw_op_id = curr_op_id.replace('-', '_') 121 | new_op_id = raw_op_id 122 | if '_' in raw_op_id: 123 | raw_op_id_iter = iter(raw_op_id.split('_')) 124 | new_op_id = next(raw_op_id_iter) 125 | for new_op_id_element in raw_op_id_iter: 126 | new_op_id += new_op_id_element.title() 127 | ''' 128 | Removes query parameters form the path. 129 | Only path elements are used in operation ids 130 | ''' 131 | paths_array = re.split(r'\?', path) 132 | path = paths_array[0] 133 | path_elements = path.replace('-', '_').split('/') 134 | path_elements_iter = iter(path_elements) 135 | for path_element in path_elements_iter: 136 | if '{' in path_element: 137 | continue 138 | if 'com' == path_element or 'vmware' == path_element: 139 | continue 140 | if path_element.lower() == raw_op_id.lower(): 141 | continue 142 | if '_' in path_element: 143 | sub_path_iter = iter(path_element.split('_')) 144 | for sub_path_element in sub_path_iter: 145 | new_op_id += sub_path_element.title() 146 | else: 147 | new_op_id += path_element.title() 148 | return new_op_id 149 | 150 | def merge_dictionaries(self, x, y): 151 | z = x.copy() # start with x's keys and values 152 | z.update(y) # modifies z with y's keys and values & returns None 153 | return z 154 | -------------------------------------------------------------------------------- /lib/rest_endpoint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/rest_endpoint/__init__.py -------------------------------------------------------------------------------- /lib/rest_endpoint/oas3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/rest_endpoint/oas3/__init__.py -------------------------------------------------------------------------------- /lib/rest_endpoint/oas3/rest_metamodel2openapi.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | from lib.rest_endpoint.rest_metamodel2spec import RestMetamodel2Spec 6 | from .rest_openapi_parameter_handler import RestOpenapiParaHandler 7 | from .rest_openapi_response_handler import RestOpenapiRespHandler 8 | 9 | rest_open_ph = RestOpenapiParaHandler() 10 | rest_open_rh = RestOpenapiRespHandler() 11 | 12 | 13 | class RestMetamodel2Openapi(RestMetamodel2Spec): 14 | 15 | def get_path( 16 | self, 17 | operation_info, 18 | http_method, 19 | url, 20 | service_name, 21 | type_dict, 22 | structure_dict, 23 | enum_dict, 24 | operation_id, 25 | error_map, 26 | show_unreleased_apis): 27 | documentation = operation_info.documentation 28 | params = operation_info.params 29 | errors = operation_info.errors 30 | output = operation_info.output 31 | http_method = http_method.lower() 32 | par_array, url = self.handle_request_mapping(url, http_method, service_name, 33 | operation_id, params, type_dict, 34 | structure_dict, enum_dict, show_unreleased_apis, rest_open_ph) 35 | response_map = rest_open_rh.populate_response_map( 36 | output, 37 | errors, 38 | error_map, 39 | type_dict, 40 | structure_dict, 41 | enum_dict, 42 | service_name, 43 | operation_id, 44 | show_unreleased_apis) 45 | 46 | path_obj = utils.build_path( 47 | service_name, 48 | http_method, 49 | url, 50 | documentation, 51 | par_array, 52 | operation_id=operation_id, 53 | responses=response_map) 54 | 55 | utils.create_req_body_from_params_list(path_obj) 56 | self.post_process_path(path_obj) 57 | path = utils.add_basic_auth(path_obj) 58 | return path 59 | 60 | def post_process_path(self, path_obj): 61 | # Temporary fixes necessary for generated spec files. 62 | # Hardcoding for now as it is not available from metadata. 63 | if path_obj['path'] == '/com/vmware/cis/session' and path_obj['method'] == 'post': 64 | header_parameter = { 65 | 'in': 'header', 66 | 'required': True, 67 | 'type': 'string', 68 | 'name': 'vmware-use-header-authn', 69 | 'description': 'Custom header to protect against CSRF attacks in browser based clients'} 70 | header_parameter['schema'] = {'type': 'string'} 71 | path_obj['parameters'] = [header_parameter] 72 | 73 | # Allow invoking $task operations from the api-explorer 74 | if path_obj['operationId'].endswith('$task'): 75 | path_obj['path'] = utils.add_query_param( 76 | path_obj['path'], 'vmw-task=true') 77 | -------------------------------------------------------------------------------- /lib/rest_endpoint/oas3/rest_openapi_parameter_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | from lib.rest_endpoint.rest_type_handler import RestTypeHandler 6 | 7 | 8 | class RestOpenapiParaHandler(): 9 | 10 | def convert_field_info_to_swagger_parameter( 11 | self, 12 | param_type, 13 | input_parameter_obj, 14 | type_dict, 15 | structure_svc, 16 | enum_svc, 17 | show_unreleased_apis): 18 | """ 19 | Converts metamodel fieldinfo to swagger parameter. 20 | """ 21 | parameter_obj = {} 22 | ref_path = "#/components/schemas/" 23 | tpHandler = RestTypeHandler(show_unreleased_apis) 24 | tpHandler.visit_type_category( 25 | input_parameter_obj.type, 26 | parameter_obj, 27 | type_dict, 28 | structure_svc, 29 | enum_svc, 30 | ref_path) 31 | if 'required' not in parameter_obj: 32 | parameter_obj['required'] = True 33 | parameter_obj['in'] = param_type 34 | parameter_obj['name'] = input_parameter_obj.name 35 | parameter_obj['description'] = input_parameter_obj.documentation 36 | # $ref should be encapsulated in 'schema' instead of parameter. -> this throws swagger validation error 37 | # hence another method is to to get data in $ref in parameter_obj 38 | # itself 39 | if '$ref' in parameter_obj: 40 | type_obj = type_dict[parameter_obj['$ref'][len(ref_path):]] 41 | description = parameter_obj['description'] 42 | if 'description' in type_obj: 43 | description = "" 44 | description = "{ 1. " + type_obj['description'] + \ 45 | " }, { 2. " + parameter_obj['description'] + " }" 46 | parameter_obj.update(type_obj) 47 | parameter_obj['description'] = description 48 | del parameter_obj['$ref'] 49 | 50 | if 'type' in parameter_obj: 51 | temp_schema = {'type': parameter_obj['type']} 52 | parameter_obj['schema'] = temp_schema 53 | del parameter_obj['type'] 54 | 55 | return parameter_obj 56 | 57 | def wrap_body_params( 58 | self, 59 | service_name, 60 | operation_name, 61 | body_param_list, 62 | type_dict, 63 | structure_svc, 64 | enum_svc, 65 | show_unreleased_apis): 66 | """ 67 | Creates a json object wrapper around request body parameters. parameter names are used as keys and the 68 | parameters as values. 69 | For instance, datacenter create operation takes CreateSpec whose parameter name is spec. 70 | This method creates a json wrapper object 71 | datacenter.create { 72 | 'spec' : {spec obj representation } 73 | } 74 | """ 75 | # todo: 76 | # not unique enough. make it unique 77 | wrapper_name = service_name + '_' + operation_name 78 | body_obj = {} 79 | properties_obj = {} 80 | body_obj['type'] = 'object' 81 | body_obj['properties'] = properties_obj 82 | required = [] 83 | ref_path = "#/components/schemas/" 84 | tpHandler = RestTypeHandler(show_unreleased_apis) 85 | for param in body_param_list: 86 | parameter_obj = {} 87 | tpHandler.visit_type_category( 88 | param.type, 89 | parameter_obj, 90 | type_dict, 91 | structure_svc, 92 | enum_svc, 93 | ref_path) 94 | parameter_obj['description'] = param.documentation 95 | properties_obj[param.name] = parameter_obj 96 | if 'required' not in parameter_obj: 97 | required.append(param.name) 98 | elif parameter_obj['required'] == 'true': 99 | required.append(param.name) 100 | 101 | if 'requestBodies' not in type_dict: 102 | type_dict['requestBodies'] = {} 103 | type_dict['requestBodies'][wrapper_name] = { 104 | 'content': { 105 | 'application/json': { 106 | 'schema': { 107 | '$ref': ref_path + wrapper_name 108 | } 109 | } 110 | } 111 | } 112 | type_dict[wrapper_name] = body_obj 113 | parameter_obj = {'$ref': "#/components/requestBodies/" + wrapper_name} 114 | 115 | return parameter_obj 116 | 117 | def flatten_query_param_spec( 118 | self, 119 | query_param_info, 120 | type_dict, 121 | structure_svc, 122 | enum_svc, 123 | show_unreleased_apis): 124 | """ 125 | Flattens query parameters specs. 126 | 1. Create a query parameter for every field in spec. 127 | Example 1: 128 | consider datacenter get which accepts optional filterspec. 129 | Optional filter) 130 | The method would convert the filterspec to 3 separate query parameters 131 | filter.datacenters, filter.names and filter.folders. 132 | Example 2: 133 | consider /vcenter/deployment/install/initial-config/remote-psc/thumbprint get 134 | which accepts parameter 135 | vcenter.deployment.install.initial_config.remote_psc.thumbprint.remote_spec. 136 | The two members defined under remote_spec 137 | address and https_port are converted to two separate query parameters 138 | address(required) and https_port(optional). 139 | 2. The field info is simple type. i.e the type is string, integer 140 | then it is converted it to swagger parameter. 141 | Example: 142 | consider /com/vmware/content/library/item get 143 | which accepts parameter 'library_id'. The field is converted 144 | to library_id query parameter. 145 | 3. This field has references to a spec but the spec is not 146 | a complex type and does not have property 'properties'. 147 | i.e the type is string, integer. The members defined under the spec are 148 | converted to query parameter. 149 | Example: 150 | consider /appliance/update/pending get which accepts two parameter 151 | 'source_type' and url. Where source_type is defined in the spec 152 | 'appliance.update.pending.source_type' and field url 153 | is of type string. 154 | The fields 'source_type' and 'url' are converted to query parameter 155 | of type string. 156 | """ 157 | prop_array = [] 158 | parameter_obj = {} 159 | ref_path = "#/components/schemas/" 160 | tpHandler = RestTypeHandler(show_unreleased_apis) 161 | tpHandler.visit_type_category( 162 | query_param_info.type, 163 | parameter_obj, 164 | type_dict, 165 | structure_svc, 166 | enum_svc, 167 | ref_path) 168 | if '$ref' in parameter_obj: 169 | reference = parameter_obj['$ref'].replace(ref_path, '') 170 | type_ref = type_dict.get(reference, None) 171 | if type_ref is None: 172 | return None 173 | if 'properties' in type_ref: 174 | for property_name, property_value in six.iteritems( 175 | type_ref['properties']): 176 | prop = { 177 | 'in': 'query', 178 | 'name': query_param_info.name + '.' + property_name} 179 | prop['schema'] = {} 180 | if 'type' in property_value: 181 | prop['schema']['type'] = property_value['type'] 182 | if property_value['type'] == 'array': 183 | prop['schema']['items'] = property_value['items'] 184 | if '$ref' in property_value['items']: 185 | ref = property_value['items']['$ref'].replace( 186 | ref_path, '') 187 | type_ref = type_dict[ref] 188 | prop['schema']['items'] = type_ref 189 | if 'description' in prop['schema']['items']: 190 | del prop['schema']['items']['description'] 191 | if 'description' in property_value: 192 | prop['description'] = property_value['description'] 193 | elif '$ref' in property_value: 194 | reference = property_value['$ref'].replace( 195 | ref_path, '') 196 | prop_obj = type_dict[reference] 197 | prop['schema'] = prop_obj 198 | if 'required' in type_ref: 199 | if property_name in type_ref['required']: 200 | prop['required'] = True 201 | else: 202 | prop['required'] = False 203 | prop_array.append(prop) 204 | else: 205 | prop = { 206 | 'in': 'query', 207 | 'name': query_param_info.name, 208 | 'description': type_ref['description'], 209 | 'schema': type_ref} 210 | prop_array.append(prop) 211 | else: 212 | parameter_obj['in'] = 'query' 213 | parameter_obj['name'] = query_param_info.name 214 | parameter_obj['description'] = query_param_info.documentation 215 | if 'required' not in parameter_obj: 216 | parameter_obj['required'] = True 217 | 218 | parameter_obj['schema'] = {"type": parameter_obj['type']} 219 | del parameter_obj['type'] 220 | 221 | prop_array.append(parameter_obj) 222 | return prop_array 223 | -------------------------------------------------------------------------------- /lib/rest_endpoint/oas3/rest_openapi_response_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import requests 5 | from six.moves import http_client 6 | from lib.rest_endpoint.rest_type_handler import RestTypeHandler 7 | 8 | 9 | class RestOpenapiRespHandler(): 10 | 11 | def populate_response_map( 12 | self, 13 | output, 14 | errors, 15 | http_error_map, 16 | type_dict, 17 | structure_svc, 18 | enum_svc, 19 | service_id, 20 | operation_id, 21 | show_unreleased_apis): 22 | 23 | response_map = {} 24 | ref_path = "#/components/schemas/" 25 | success_response = { 26 | 'description': output.documentation, 27 | 'content': { 28 | 'application/json': { 29 | } 30 | } 31 | } 32 | schema = {} 33 | tpHandler = RestTypeHandler(show_unreleased_apis) 34 | tpHandler.visit_type_category( 35 | output.type, 36 | schema, 37 | type_dict, 38 | structure_svc, 39 | enum_svc, 40 | ref_path) 41 | # if type of schema is void, don't include it. 42 | # this prevents showing response as void in swagger-ui 43 | if schema is not None: 44 | if not ('type' in schema and schema['type'] == 'void'): 45 | resp = { 46 | 'type': 'object', 47 | 'properties': {'value': schema}, 48 | 'required': ['value'] 49 | } 50 | # get response object name 51 | if operation_id == 'get': 52 | type_name = service_id 53 | else: 54 | type_name = service_id + '.' + operation_id 55 | 56 | type_name = type_name + '_resp' 57 | 58 | if type_name not in type_dict: 59 | type_dict[type_name] = resp 60 | resp = {"$ref": ref_path + type_name} 61 | success_response['content']['application/json']['schema'] = resp 62 | 63 | # success response is not mapped through metamodel. 64 | # hardcode it for now. 65 | response_map[requests.codes.ok] = success_response 66 | 67 | for error in errors: 68 | status_code = http_error_map.error_rest_map.get( 69 | error.structure_id, 70 | http_client.INTERNAL_SERVER_ERROR) 71 | tpHandler.check_type( 72 | 'com.vmware.vapi.structure', 73 | error.structure_id, 74 | type_dict, 75 | structure_svc, 76 | enum_svc, 77 | ref_path) 78 | schema_obj = { 79 | 'type': 'object', 'properties': { 80 | 'type': { 81 | 'type': 'string'}, 'value': { 82 | '$ref': ref_path + error.structure_id}}} 83 | type_dict[error.structure_id + '_error'] = schema_obj 84 | response_obj = { 85 | 'description': error.documentation, 86 | 'content': { 87 | 'application/json': { 88 | 'schema': {'$ref': ref_path + error.structure_id} 89 | } 90 | } 91 | } 92 | response_map[status_code] = response_obj 93 | 94 | return response_map 95 | -------------------------------------------------------------------------------- /lib/rest_endpoint/rest_deprecation_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | class RestDeprecationHandler: 5 | 6 | def __init__(self, replacement_dict): 7 | ''' 8 | service -> operation -> (method, raplacement path) ; for deprecated /rest 9 | ''' 10 | self.replacement_dict = replacement_dict 11 | 12 | def add_deprecation_information(self, path_obj, package_name, service_name): 13 | replacement_path = "" 14 | path_obj["deprecated"] = True 15 | 16 | # construct file name 17 | api_file_name = "api_" + package_name + ".json" 18 | 19 | # Could be a more intelligent resolution - guessing based on key words? 20 | operation_map = self.replacement_dict.get(service_name) 21 | if operation_map is not None and "operationId" in path_obj: 22 | method_path_tuple = operation_map.get(path_obj["operationId"]) 23 | if method_path_tuple is not None: 24 | # get concrete path and format accordingly 25 | replacement_path = method_path_tuple[1] 26 | replacement_path = replacement_path.replace("/", "~1") 27 | replacement_path = api_file_name + "#/paths/~1api" + replacement_path + "/" + method_path_tuple[0] 28 | 29 | path_obj["x-vmw-deprecated"] = {"replacement": replacement_path} 30 | 31 | -------------------------------------------------------------------------------- /lib/rest_endpoint/rest_metadata_processor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | from lib import utils 6 | from lib.metadata_processor import MetadataProcessor 7 | from .oas3.rest_metamodel2openapi import RestMetamodel2Openapi 8 | from .swagger2.rest_metamodel2swagger import RestMetamodel2Swagger 9 | 10 | swagg = RestMetamodel2Swagger() 11 | openapi = RestMetamodel2Openapi() 12 | 13 | 14 | class RestMetadataProcessor(MetadataProcessor): 15 | def __init__(self): 16 | pass 17 | 18 | def get_path_and_type_dicts( 19 | self, 20 | package_name, 21 | service_urls, 22 | structure_dict, 23 | enum_dict, 24 | service_dict, 25 | service_url_dict, 26 | http_error_map, 27 | rest_navigation_handler, 28 | show_unreleased_apis, 29 | spec, 30 | auth_navigator=None, 31 | deprecation_handler=None): 32 | 33 | print('processing package ' + package_name + os.linesep) 34 | type_dict = {} 35 | path_list = [] 36 | 37 | for service_url in service_urls: 38 | service_name, service_end_point = service_url_dict.get( 39 | service_url, None) 40 | service_info = service_dict.get(service_name, None) 41 | if service_info is None: 42 | continue 43 | if (not show_unreleased_apis) and utils.is_filtered(service_info.metadata): 44 | continue 45 | if self.contains_rm_annotation(service_info): 46 | for operation in service_info.operations.values(): 47 | url, method = self.find_url_method(operation) 48 | operation_id = operation.name 49 | op_metadata = service_info.operations[operation_id].metadata 50 | if (not show_unreleased_apis) and utils.is_filtered(op_metadata): 51 | continue 52 | operation_info = service_info.operations.get(operation_id) 53 | 54 | if spec == '2': 55 | path = swagg.get_path( 56 | operation_info, 57 | method, 58 | url, 59 | service_name, 60 | type_dict, 61 | structure_dict, 62 | enum_dict, 63 | operation_id, 64 | http_error_map, 65 | show_unreleased_apis) 66 | 67 | if auth_navigator is not None: 68 | scheme_set = auth_navigator.find_schemes_set(operation_id, service_name, package_name) 69 | if scheme_set is not None and len(scheme_set) != 0: 70 | swagg.decorate_path_with_security(path, scheme_set) 71 | 72 | if spec == '3': 73 | path = openapi.get_path( 74 | operation_info, 75 | method, 76 | url, 77 | service_name, 78 | type_dict, 79 | structure_dict, 80 | enum_dict, 81 | operation_id, 82 | http_error_map, 83 | show_unreleased_apis) 84 | 85 | if deprecation_handler is not None and service_end_point == "/deprecated": 86 | deprecation_handler.add_deprecation_information(path, package_name, service_name) 87 | path_list.append(path) 88 | continue 89 | # use rest navigation service to get the REST mappings for a 90 | # service. 91 | service_operations = rest_navigation_handler.get_service_operations(service_url) 92 | if service_operations is None: 93 | continue 94 | 95 | for service_operation in service_operations: 96 | service_name = service_operation['service'] 97 | # service_info must be re-assigned when service_operations are obtained through ?~method=OPTIONS. 98 | # this is because all service operations matching the prefix of the service is returned instead of returning 99 | # only operations which has exact match. 100 | # for example OPTIONS on com.vmware.content.library returns operations from following services 101 | # instead of just com.vmware.content.library.item 102 | # com.vmware.content.library.item.storage 103 | # com.vmware.content.library.item 104 | # com.vmware.content.library.item.file 105 | # com.vmware.content.library.item.update_session 106 | # com.vmware.content.library.item.updatesession.file 107 | service_info = service_dict.get(service_name, None) 108 | if service_info is None: 109 | continue 110 | operation_id = service_operation['name'] 111 | if operation_id not in service_info.operations: 112 | continue 113 | op_metadata = service_info.operations[operation_id].metadata 114 | if (not show_unreleased_apis) and utils.is_filtered(op_metadata): 115 | continue 116 | url, method = self.find_url(service_operation['links']) 117 | url = self.get_service_path_from_service_url( 118 | url, rest_navigation_handler.get_rest_navigation_url()) 119 | operation_info = service_info.operations.get(operation_id) 120 | 121 | if spec == '2': 122 | path = swagg.get_path( 123 | operation_info, 124 | method, 125 | url, 126 | service_name, 127 | type_dict, 128 | structure_dict, 129 | enum_dict, 130 | operation_id, 131 | http_error_map, 132 | show_unreleased_apis) 133 | 134 | if auth_navigator is not None: 135 | scheme_set = auth_navigator.find_schemes_set(operation_id, service_name, package_name) 136 | if scheme_set is not None and len(scheme_set) != 0: 137 | swagg.decorate_path_with_security(path, scheme_set) 138 | 139 | if spec == '3': 140 | path = openapi.get_path( 141 | operation_info, 142 | method, 143 | url, 144 | service_name, 145 | type_dict, 146 | structure_dict, 147 | enum_dict, 148 | operation_id, 149 | http_error_map, 150 | show_unreleased_apis) 151 | 152 | if deprecation_handler is not None and service_end_point == "/deprecated": 153 | deprecation_handler.add_deprecation_information(path, package_name, service_name) 154 | path_list.append(path) 155 | path_dict = self.convert_path_list_to_path_map(path_list) 156 | self.cleanup(path_dict=path_dict, type_dict=type_dict) 157 | return path_dict, type_dict 158 | 159 | def contains_rm_annotation(self, service_info): 160 | for operation in service_info.operations.values(): 161 | if 'RequestMapping' not in operation.metadata: 162 | return False 163 | return True 164 | 165 | def find_url_method(self, opinfo): 166 | """ 167 | Given OperationInfo, find url and method if it exists 168 | :param opinfo: 169 | :return: 170 | """ 171 | params = None 172 | url = None 173 | method = None 174 | if 'RequestMapping' in opinfo.metadata: 175 | element_map = opinfo.metadata['RequestMapping'] 176 | if 'value' in element_map.elements: 177 | element_value = element_map.elements['value'] 178 | url = self.find_string_element_value(element_value) 179 | if 'method' in element_map.elements: 180 | element_value = element_map.elements['method'] 181 | method = self.find_string_element_value(element_value) 182 | if 'params' in element_map.elements: 183 | element_value = element_map.elements['params'] 184 | params = self.find_string_element_value(element_value) 185 | if params is not None: 186 | url = url + '?' + params 187 | 188 | url = "/rest" + url 189 | return url, method 190 | 191 | def find_string_element_value(self, element_value): 192 | """ 193 | if input parameter is a path variable, this method 194 | determines name of the path variable. 195 | """ 196 | if element_value is dict: 197 | if element_value['type'] == 'STRING': 198 | return element_value['string_value'] 199 | else: 200 | return element_value.string_value 201 | -------------------------------------------------------------------------------- /lib/rest_endpoint/rest_metamodel2spec.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | 6 | 7 | class RestMetamodel2Spec(): 8 | 9 | def get_path( 10 | self, 11 | operation_info, 12 | http_method, 13 | url, 14 | service_name, 15 | type_dict, 16 | structure_dict, 17 | enum_dict, 18 | operation_id, 19 | error_map, 20 | show_unreleased_apis): 21 | pass 22 | 23 | def handle_request_mapping( 24 | self, 25 | url, 26 | method_type, 27 | service_name, 28 | operation_name, 29 | params_metadata, 30 | type_dict, 31 | structure_svc, 32 | enum_svc, 33 | show_unreleased_apis, 34 | spec): 35 | if method_type in ('post', 'put', 'patch'): 36 | return self.process_put_post_patch_request( 37 | url, 38 | service_name, 39 | operation_name, 40 | params_metadata, 41 | type_dict, 42 | structure_svc, 43 | enum_svc, 44 | show_unreleased_apis, 45 | spec) 46 | if method_type == 'get': 47 | return self.process_get_request( 48 | url, 49 | params_metadata, 50 | type_dict, 51 | structure_svc, 52 | enum_svc, 53 | show_unreleased_apis, 54 | spec) 55 | if method_type == 'delete': 56 | return self.process_delete_request( 57 | url, 58 | params_metadata, 59 | type_dict, 60 | structure_svc, 61 | enum_svc, 62 | show_unreleased_apis, 63 | spec) 64 | 65 | def process_put_post_patch_request( 66 | self, 67 | url, 68 | service_name, 69 | operation_name, 70 | params, 71 | type_dict, 72 | structure_svc, 73 | enum_svc, 74 | show_unreleased_apis, 75 | spec): 76 | """ 77 | Handles http post/put/patch request. 78 | todo: handle query, formData and header parameters 79 | """ 80 | # Path 81 | path_param_list, other_param_list, new_url = utils.extract_path_parameters( 82 | params, url) 83 | par_array = [] 84 | for field_info in path_param_list: 85 | parx = spec.convert_field_info_to_swagger_parameter( 86 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 87 | par_array.append(parx) 88 | 89 | # Body 90 | body_param_list = other_param_list 91 | 92 | if body_param_list: 93 | parx = spec.wrap_body_params( 94 | service_name, 95 | operation_name, 96 | body_param_list, 97 | type_dict, 98 | structure_svc, 99 | enum_svc, 100 | show_unreleased_apis) 101 | if parx is not None: 102 | par_array.append(parx) 103 | 104 | return par_array, new_url 105 | 106 | def process_get_request( 107 | self, 108 | url, 109 | params, 110 | type_dict, 111 | structure_svc, 112 | enum_svc, 113 | show_unreleased_apis, 114 | spec): 115 | param_array = [] 116 | path_param_list, other_params_list, new_url = utils.extract_path_parameters( 117 | params, url) 118 | 119 | for field_info in path_param_list: 120 | parameter_obj = spec.convert_field_info_to_swagger_parameter( 121 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 122 | param_array.append(parameter_obj) 123 | 124 | # process query parameters 125 | for field_info in other_params_list: 126 | # See documentation of method flatten_query_param_spec to understand 127 | # handling of all the query parameters; filter as well as non 128 | # filter 129 | flattened_params = spec.flatten_query_param_spec( 130 | field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 131 | if flattened_params is not None: 132 | param_array = param_array + flattened_params 133 | return param_array, new_url 134 | 135 | def process_delete_request( 136 | self, 137 | url, 138 | params, 139 | type_dict, 140 | structure_svc, 141 | enum_svc, 142 | show_unreleased_apis, 143 | spec): 144 | path_param_list, other_params, new_url = utils.extract_path_parameters( 145 | params, url) 146 | param_array = [] 147 | for field_info in path_param_list: 148 | parx = spec.convert_field_info_to_swagger_parameter( 149 | 'path', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 150 | param_array.append(parx) 151 | for field_info in other_params: 152 | parx = spec.convert_field_info_to_swagger_parameter( 153 | 'query', field_info, type_dict, structure_svc, enum_svc, show_unreleased_apis) 154 | param_array.append(parx) 155 | return param_array, new_url 156 | 157 | def post_process_path(self, path_obj): 158 | pass 159 | -------------------------------------------------------------------------------- /lib/rest_endpoint/rest_navigation_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | 6 | class RestNavigationHandler: 7 | 8 | def __init__(self, rest_navigation_url): 9 | self.rest_navigation_url = rest_navigation_url 10 | 11 | def get_service_operations(self, service_url): 12 | if self.rest_navigation_url not in service_url: 13 | return utils.get_json(self.rest_navigation_url + service_url + '?~method=OPTIONS', False) 14 | return utils.get_json(service_url + '?~method=OPTIONS', False) 15 | 16 | def get_rest_navigation_url(self): 17 | return self.rest_navigation_url 18 | -------------------------------------------------------------------------------- /lib/rest_endpoint/rest_type_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils 5 | from lib.type_handler_common import TypeHandlerCommon 6 | 7 | 8 | class RestTypeHandler(TypeHandlerCommon): 9 | 10 | def __init__(self, show_unreleased_apis): 11 | TypeHandlerCommon.__init__(self, show_unreleased_apis) 12 | 13 | def visit_generic( 14 | self, 15 | generic_instantiation, 16 | new_prop, 17 | type_dict, 18 | structure_svc, 19 | enum_svc, 20 | ref_path): 21 | if generic_instantiation.generic_type == 'OPTIONAL': 22 | new_prop['required'] = False 23 | self.visit_type_category( 24 | generic_instantiation.element_type, 25 | new_prop, 26 | type_dict, 27 | structure_svc, 28 | enum_svc, 29 | ref_path) 30 | elif generic_instantiation.generic_type == 'LIST': 31 | new_prop['type'] = 'array' 32 | self.visit_type_category( 33 | generic_instantiation.element_type, 34 | new_prop, 35 | type_dict, 36 | structure_svc, 37 | enum_svc, 38 | ref_path) 39 | elif generic_instantiation.generic_type == 'SET': 40 | new_prop['type'] = 'array' 41 | new_prop['uniqueItems'] = True 42 | self.visit_type_category( 43 | generic_instantiation.element_type, 44 | new_prop, 45 | type_dict, 46 | structure_svc, 47 | enum_svc, 48 | ref_path) 49 | elif generic_instantiation.generic_type == 'MAP': 50 | # Have static key/value pair object maping for /rest paths 51 | # while use additionalProperties for /api paths 52 | new_type = {'type': 'object', 'properties': {}} 53 | if generic_instantiation.map_key_type.category == 'USER_DEFINED': 54 | res_id = generic_instantiation.map_key_type.user_defined_type.resource_id 55 | res_type = generic_instantiation.map_key_type.user_defined_type.resource_type 56 | new_type['properties']['key'] = {'$ref': ref_path + res_id} 57 | self.check_type( 58 | res_type, 59 | res_id, 60 | type_dict, 61 | structure_svc, 62 | enum_svc, 63 | ref_path) 64 | else: 65 | new_type['properties']['key'] = { 66 | 'type': utils.metamodel_to_swagger_type_converter( 67 | generic_instantiation.map_key_type.builtin_type)[0]} 68 | 69 | if generic_instantiation.map_value_type.category == 'USER_DEFINED': 70 | new_type['properties']['value'] = { 71 | '$ref': ref_path + generic_instantiation.map_value_type.user_defined_type.resource_id} 72 | res_type = generic_instantiation.map_value_type.user_defined_type.resource_type 73 | res_id = generic_instantiation.map_value_type.user_defined_type.resource_id 74 | self.check_type( 75 | res_type, 76 | res_id, 77 | type_dict, 78 | structure_svc, 79 | enum_svc, 80 | ref_path) 81 | 82 | elif generic_instantiation.map_value_type.category == 'BUILTIN': 83 | new_type['properties']['value'] = { 84 | 'type': utils.metamodel_to_swagger_type_converter( 85 | generic_instantiation.map_value_type.builtin_type)[0]} 86 | 87 | elif generic_instantiation.map_value_type.category == 'GENERIC': 88 | temp_new_type = {} 89 | self.visit_generic( 90 | generic_instantiation.map_value_type.generic_instantiation, 91 | temp_new_type, 92 | type_dict, 93 | structure_svc, 94 | enum_svc, 95 | ref_path) 96 | new_type['properties']['value'] = temp_new_type 97 | 98 | new_prop['type'] = 'array' 99 | new_prop['items'] = new_type 100 | 101 | if 'additionalProperties' in new_type: 102 | if not new_type['additionalProperties'].get('required', True): 103 | del new_type['additionalProperties']['required'] 104 | 105 | if '$ref' in new_prop: 106 | del new_prop['$ref'] 107 | -------------------------------------------------------------------------------- /lib/rest_endpoint/swagger2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware/vmware-openapi-generator/54cf631512e69ca731a1fd5c0662b647833e7e2d/lib/rest_endpoint/swagger2/__init__.py -------------------------------------------------------------------------------- /lib/rest_endpoint/swagger2/rest_metamodel2swagger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | from lib import utils, authentication_metadata_processing 5 | from lib.rest_endpoint.rest_metamodel2spec import RestMetamodel2Spec 6 | from .rest_swagger_parameter_handler import RestSwaggerParaHandler 7 | from .rest_swagger_response_handler import RestSwaggerRespHandler 8 | 9 | rest_swagg_ph = RestSwaggerParaHandler() 10 | rest_swagg_rh = RestSwaggerRespHandler() 11 | 12 | 13 | class RestMetamodel2Swagger(RestMetamodel2Spec): 14 | 15 | def get_path( 16 | self, 17 | operation_info, 18 | http_method, 19 | url, 20 | service_name, 21 | type_dict, 22 | structure_dict, 23 | enum_dict, 24 | operation_id, 25 | error_map, 26 | show_unreleased_apis): 27 | documentation = operation_info.documentation 28 | params = operation_info.params 29 | errors = operation_info.errors 30 | output = operation_info.output 31 | http_method = http_method.lower() 32 | par_array, url = self.handle_request_mapping(url, http_method, service_name, 33 | operation_id, params, type_dict, 34 | structure_dict, enum_dict, show_unreleased_apis, rest_swagg_ph) 35 | response_map = rest_swagg_rh.populate_response_map( 36 | output, 37 | errors, 38 | error_map, 39 | type_dict, 40 | structure_dict, 41 | enum_dict, 42 | service_name, 43 | operation_id, 44 | show_unreleased_apis) 45 | 46 | path_obj = utils.build_path( 47 | service_name, 48 | http_method, 49 | url, 50 | documentation, 51 | par_array, 52 | operation_id=operation_id, 53 | responses=response_map) 54 | self.post_process_path(path_obj) 55 | path = utils.add_basic_auth(path_obj) 56 | return path 57 | 58 | def post_process_path(self, path_obj): 59 | # Temporary fixes necessary for generated spec files. 60 | # Hardcoding for now as it is not available from metadata. 61 | if path_obj['path'] == '/com/vmware/cis/session' and path_obj['method'] == 'post': 62 | header_parameter = { 63 | 'in': 'header', 64 | 'required': True, 65 | 'type': 'string', 66 | 'name': 'vmware-use-header-authn', 67 | 'description': 'Custom header to protect against CSRF attacks in browser based clients'} 68 | header_parameter['type'] = 'string' 69 | path_obj['parameters'] = [header_parameter] 70 | 71 | # Allow invoking $task operations from the api-explorer 72 | if path_obj['operationId'].endswith('$task'): 73 | path_obj['path'] = utils.add_query_param( 74 | path_obj['path'], 'vmw-task=true') 75 | 76 | def decorate_path_with_security(self, path_obj, scheme_set): 77 | security_schemes = [] 78 | if authentication_metadata_processing.session_id_scheme in scheme_set: 79 | security_schemes.append({"session_id": []}) 80 | if authentication_metadata_processing.basic_auth_scheme in scheme_set: 81 | security_schemes.append({"basic_auth": []}) 82 | if security_schemes: 83 | path_obj["security"] = security_schemes 84 | -------------------------------------------------------------------------------- /lib/rest_endpoint/swagger2/rest_swagger_parameter_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | from lib.rest_endpoint.rest_type_handler import RestTypeHandler 6 | 7 | 8 | class RestSwaggerParaHandler(): 9 | 10 | def convert_field_info_to_swagger_parameter( 11 | self, 12 | param_type, 13 | input_parameter_obj, 14 | type_dict, 15 | structure_svc, 16 | enum_svc, 17 | show_unreleased_apis): 18 | """ 19 | Converts metamodel fieldinfo to swagger parameter. 20 | """ 21 | parameter_obj = {} 22 | ref_path = "#/definitions/" 23 | tpHandler = RestTypeHandler(show_unreleased_apis) 24 | tpHandler.visit_type_category( 25 | input_parameter_obj.type, 26 | parameter_obj, 27 | type_dict, 28 | structure_svc, 29 | enum_svc, 30 | ref_path) 31 | if 'required' not in parameter_obj: 32 | parameter_obj['required'] = True 33 | parameter_obj['in'] = param_type 34 | parameter_obj['name'] = input_parameter_obj.name 35 | parameter_obj['description'] = input_parameter_obj.documentation 36 | # $ref should be encapsulated in 'schema' instead of parameter. -> this throws swagger validation error 37 | # hence another method is to to get data in $ref in parameter_obj 38 | # itself 39 | if '$ref' in parameter_obj: 40 | type_obj = type_dict[parameter_obj['$ref'][len(ref_path):]] 41 | description = parameter_obj['description'] 42 | if 'description' in type_obj: 43 | description = "" 44 | description = "{ 1. " + type_obj['description'] + \ 45 | " }, { 2. " + parameter_obj['description'] + " }" 46 | parameter_obj.update(type_obj) 47 | parameter_obj['description'] = description 48 | del parameter_obj['$ref'] 49 | return parameter_obj 50 | 51 | def wrap_body_params( 52 | self, 53 | service_name, 54 | operation_name, 55 | body_param_list, 56 | type_dict, 57 | structure_svc, 58 | enum_svc, 59 | show_unreleased_apis): 60 | """ 61 | Creates a json object wrapper around request body parameters. parameter names are used as keys and the 62 | parameters as values. 63 | For instance, datacenter create operation takes CreateSpec whose parameter name is spec. 64 | This method creates a json wrapper object 65 | datacenter.create { 66 | 'spec' : {spec obj representation } 67 | } 68 | """ 69 | # todo: 70 | # not unique enough. make it unique 71 | wrapper_name = service_name + '_' + operation_name 72 | body_obj = {} 73 | properties_obj = {} 74 | body_obj['type'] = 'object' 75 | body_obj['properties'] = properties_obj 76 | required = [] 77 | ref_path = "#/definitions/" 78 | tpHandler = RestTypeHandler(show_unreleased_apis) 79 | for param in body_param_list: 80 | parameter_obj = {} 81 | tpHandler.visit_type_category( 82 | param.type, 83 | parameter_obj, 84 | type_dict, 85 | structure_svc, 86 | enum_svc, 87 | ref_path) 88 | parameter_obj['description'] = param.documentation 89 | properties_obj[param.name] = parameter_obj 90 | if 'required' not in parameter_obj: 91 | required.append(param.name) 92 | elif parameter_obj['required'] == 'true': 93 | required.append(param.name) 94 | 95 | parameter_obj = {'in': 'body', 'name': 'request_body'} 96 | if len(required) > 0: 97 | body_obj['required'] = required 98 | parameter_obj['required'] = True 99 | elif 'required' in body_obj: 100 | del body_obj['required'] 101 | 102 | type_dict[wrapper_name] = body_obj 103 | 104 | schema_obj = {'$ref': ref_path + wrapper_name} 105 | parameter_obj['schema'] = schema_obj 106 | 107 | return parameter_obj 108 | 109 | def flatten_query_param_spec( 110 | self, 111 | query_param_info, 112 | type_dict, 113 | structure_svc, 114 | enum_svc, 115 | show_unreleased_apis): 116 | """ 117 | Flattens query parameters specs. 118 | 1. Create a query parameter for every field in spec. 119 | Example 1: 120 | consider datacenter get which accepts optional filterspec. 121 | Optional filter) 122 | The method would convert the filterspec to 3 separate query parameters 123 | filter.datacenters, filter.names and filter.folders. 124 | Example 2: 125 | consider /vcenter/deployment/install/initial-config/remote-psc/thumbprint get 126 | which accepts parameter 127 | vcenter.deployment.install.initial_config.remote_psc.thumbprint.remote_spec. 128 | The two members defined under remote_spec 129 | address and https_port are converted to two separate query parameters 130 | address(required) and https_port(optional). 131 | 2. The field info is simple type. i.e the type is string, integer 132 | then it is converted it to swagger parameter. 133 | Example: 134 | consider /com/vmware/content/library/item get 135 | which accepts parameter 'library_id'. The field is converted 136 | to library_id query parameter. 137 | 3. This field has references to a spec but the spec is not 138 | a complex type and does not have property 'properties'. 139 | i.e the type is string, integer. The members defined under the spec are 140 | converted to query parameter. 141 | Example: 142 | consider /appliance/update/pending get which accepts two parameter 143 | 'source_type' and url. Where source_type is defined in the spec 144 | 'appliance.update.pending.source_type' and field url 145 | is of type string. 146 | The fields 'source_type' and 'url' are converted to query parameter 147 | of type string. 148 | """ 149 | prop_array = [] 150 | parameter_obj = {} 151 | ref_path = "#/definitions/" 152 | tpHandler = RestTypeHandler(show_unreleased_apis) 153 | tpHandler.visit_type_category( 154 | query_param_info.type, 155 | parameter_obj, 156 | type_dict, 157 | structure_svc, 158 | enum_svc, 159 | ref_path) 160 | if '$ref' in parameter_obj: 161 | reference = parameter_obj['$ref'].replace(ref_path, '') 162 | type_ref = type_dict.get(reference, None) 163 | if type_ref is None: 164 | return None 165 | if 'properties' in type_ref: 166 | for property_name, property_value in six.iteritems( 167 | type_ref['properties']): 168 | prop = { 169 | 'in': 'query', 170 | 'name': query_param_info.name + '.' + property_name} 171 | if 'type' in property_value: 172 | prop['type'] = property_value['type'] 173 | if prop['type'] == 'array': 174 | prop['collectionFormat'] = 'multi' 175 | prop['items'] = property_value['items'] 176 | if '$ref' in property_value['items']: 177 | ref = property_value['items']['$ref'].replace( 178 | ref_path, '') 179 | type_ref = type_dict[ref] 180 | prop['items'] = type_ref 181 | if 'description' in prop['items']: 182 | del prop['items']['description'] 183 | if 'description' in property_value: 184 | prop['description'] = property_value['description'] 185 | elif '$ref' in property_value: 186 | reference = property_value['$ref'].replace( 187 | ref_path, '') 188 | prop_obj = type_dict[reference] 189 | if 'type' in prop_obj: 190 | prop['type'] = prop_obj['type'] 191 | # Query parameter's type is object, Coverting it to 192 | # string given type object for query is not 193 | # supported by swagger 2.0. 194 | if prop['type'] == "object": 195 | prop['type'] = "string" 196 | if 'enum' in prop_obj: 197 | prop['enum'] = prop_obj['enum'] 198 | if 'description' in prop_obj: 199 | prop['description'] = prop_obj['description'] 200 | if 'required' in type_ref: 201 | if property_name in type_ref['required']: 202 | prop['required'] = True 203 | else: 204 | prop['required'] = False 205 | prop_array.append(prop) 206 | else: 207 | prop = { 208 | 'in': 'query', 209 | 'name': query_param_info.name, 210 | 'description': type_ref['description'], 211 | 'type': type_ref['type']} 212 | if 'enum' in type_ref: 213 | prop['enum'] = type_ref['enum'] 214 | if 'required' not in parameter_obj: 215 | prop['required'] = True 216 | else: 217 | prop['required'] = parameter_obj['required'] 218 | prop_array.append(prop) 219 | else: 220 | parameter_obj['in'] = 'query' 221 | parameter_obj['name'] = query_param_info.name 222 | parameter_obj['description'] = query_param_info.documentation 223 | if 'required' not in parameter_obj: 224 | parameter_obj['required'] = True 225 | prop_array.append(parameter_obj) 226 | return prop_array 227 | -------------------------------------------------------------------------------- /lib/rest_endpoint/swagger2/rest_swagger_response_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import requests 5 | from six.moves import http_client 6 | from lib.rest_endpoint.rest_type_handler import RestTypeHandler 7 | 8 | 9 | class RestSwaggerRespHandler(): 10 | 11 | def populate_response_map( 12 | self, 13 | output, 14 | errors, 15 | http_error_map, 16 | type_dict, 17 | structure_svc, 18 | enum_svc, 19 | service_id, 20 | operation_id, 21 | show_unreleased_apis): 22 | 23 | response_map = {} 24 | ref_path = "#/definitions/" 25 | success_response = {'description': output.documentation} 26 | schema = {} 27 | tpHandler = RestTypeHandler(show_unreleased_apis) 28 | tpHandler.visit_type_category( 29 | output.type, 30 | schema, 31 | type_dict, 32 | structure_svc, 33 | enum_svc, 34 | ref_path) 35 | # if type of schema is void, don't include it. 36 | # this prevents showing response as void in swagger-ui 37 | if schema is not None: 38 | if not ('type' in schema and schema['type'] == 'void'): 39 | # Handling Value wrappers for /rest and /api 40 | resp = { 41 | 'type': 'object', 42 | 'properties': {'value': schema}, 43 | 'required': ['value'] 44 | } 45 | if operation_id == 'get': 46 | type_name = service_id 47 | else: 48 | type_name = service_id + '.' + operation_id 49 | 50 | type_name = type_name + '_resp' 51 | 52 | if type_name not in type_dict: 53 | type_dict[type_name] = resp 54 | resp = {"$ref": ref_path + type_name} 55 | success_response['schema'] = resp 56 | 57 | # success response is not mapped through metamodel. 58 | # hardcode it for now. 59 | response_map[requests.codes.ok] = success_response 60 | 61 | for error in errors: 62 | status_code = http_error_map.error_rest_map.get( 63 | error.structure_id, 64 | http_client.INTERNAL_SERVER_ERROR) 65 | tpHandler.check_type( 66 | 'com.vmware.vapi.structure', 67 | error.structure_id, 68 | type_dict, 69 | structure_svc, 70 | enum_svc, 71 | ref_path) 72 | schema_obj = { 73 | 'type': 'object', 'properties': { 74 | 'type': { 75 | 'type': 'string'}, 'value': { 76 | '$ref': ref_path + error.structure_id}}} 77 | type_dict[error.structure_id + '_error'] = schema_obj 78 | response_obj = { 79 | 'description': error.documentation, 'schema': { 80 | '$ref': ref_path + error.structure_id + '_error'}} 81 | response_map[status_code] = response_obj 82 | 83 | return response_map 84 | -------------------------------------------------------------------------------- /lib/swagger_final_path_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import os 5 | import re 6 | import collections 7 | from lib import utils 8 | from lib.path_processing import PathProcessing 9 | 10 | 11 | class SwaggerPathProcessing(PathProcessing): 12 | def __init__(self): 13 | pass 14 | 15 | def process_output( 16 | self, 17 | path_dict, 18 | type_dict, 19 | output_dir, 20 | output_filename, 21 | gen_unique_op_id, 22 | prefix=''): 23 | description_map = utils.load_description() 24 | 25 | file_prefix = '' 26 | if prefix != '': 27 | file_prefix = prefix + "_" 28 | 29 | swagger_template = { 30 | 'swagger': '2.0', 31 | 'info': { 32 | 'description': description_map.get( 33 | output_filename, 34 | ''), 35 | 'title': utils.remove_curly_braces(output_filename), 36 | 'version': '2.0.0'}, 37 | 'host': '', 38 | 'securityDefinitions': { 39 | "session_id": { 40 | "in": "header", 41 | "name": "vmware-api-session-id", 42 | "type": "apiKey" 43 | }, 44 | 'basic_auth': { 45 | 'type': 'basic' 46 | }}, 47 | 'basePath': '', 48 | 'produces': [ 49 | 'application/json' 50 | ], 51 | "consumes": [ 52 | "application/json" 53 | ], 54 | 'tags': [], 55 | 'schemes': [ 56 | 'https', 57 | 'http'], 58 | 'paths': collections.OrderedDict( 59 | sorted( 60 | path_dict.items())), 61 | 'definitions': collections.OrderedDict( 62 | sorted( 63 | type_dict.items()))} 64 | 65 | if not os.path.exists(output_dir): 66 | os.mkdir(output_dir) 67 | 68 | utils.write_json_data_to_file( 69 | output_dir + 70 | os.path.sep + 71 | file_prefix + 72 | utils.remove_curly_braces(output_filename) + 73 | '.json', 74 | swagger_template) 75 | 76 | def remove_query_params(self, path_dict): 77 | """ 78 | Swagger/Open API specification prohibits appending query parameter to the request mapping path. 79 | 80 | Duplicate paths in Open API : 81 | Since request mapping paths are keys in the Open Api JSON, there is no scope of duplicate request mapping paths 82 | 83 | Partial Duplicates in Open API: APIs which have same request mapping paths but different HTTP Operations. 84 | 85 | Such Operations can be merged together under one path 86 | eg: Consider these two paths 87 | /A/B/C : [POST] 88 | /A/B/C : [PUT] 89 | On merging these, the new path would look like: 90 | /A/B/C : [POST, PUT] 91 | 92 | Absolute Duplicates in Open API: APIs which have same request mapping path and HTTP Operation(s) 93 | eg: Consider two paths 94 | /A/B/C : [POST, PUT] 95 | /A/B/C : [PUT] 96 | Such paths can not co-exist in the same Open API definition. 97 | 98 | This method attempts to move query parameters from request mapping url to parameter section. 99 | 100 | There are 4 possibilities which may arise on removing the query parameter from request mapping path: 101 | 102 | 1. Absolute Duplicate 103 | The combination of path and the HTTP Operation Type(s)are same to that of an existing path: 104 | Handling Such APIs is Out of Scope of this method. Such APIs will appear in the Open API definition unchanged. 105 | Example : 106 | /com/vmware/cis/session?~action=get : [POST] 107 | /com/vmware/cis/session : [POST, DELETE] 108 | 2. Partial Duplicate: 109 | The Paths are same but the HTTP operations are Unique: 110 | Handling Such APIs involves adding the Operations of the new duplicate path to that of the existing path 111 | Example : 112 | /cis/tasks/{task}?action=cancel : [POST] 113 | /cis/tasks/{task} : [GET] 114 | 3. New Unique Path: 115 | The new path is not a duplicate of any path in the current Open API definition. 116 | The Path is changed to new path by trimming off the path post '?' 117 | 118 | 4. The duplicate paths are formed when two paths with QueryParameters are fixed 119 | All the scenarios under 1, 2 and 3 are possible. 120 | Example : 121 | /com/vmware/cis/tagging/tag-association/id:{tag_id}?~action=detach-tag-from-multiple-objects 122 | /com/vmware/cis/tagging/tag-association/id:{tag_id}?~action=list-attached-objects 123 | :param path_dict: 124 | """ 125 | paths_to_delete = [] 126 | for old_path in list(path_dict.keys()): 127 | http_operations = path_dict[old_path] 128 | if '?' in old_path: 129 | paths_array = re.split(r'\?', old_path) 130 | new_path = paths_array[0] 131 | 132 | query_param = [] 133 | for query_parameter in paths_array[1].split('&'): 134 | key_value = query_parameter.split('=') 135 | q_param = { 136 | 'name': key_value[0], 137 | 'in': 'query', 138 | 'description': key_value[0], 139 | 'required': True, 140 | 'type': 'string'} 141 | if len(key_value) == 2: 142 | q_param['description'] = key_value[0] + '=' + key_value[1] 143 | q_param['enum'] = [key_value[1]] 144 | 145 | query_param.append(q_param) 146 | 147 | if new_path in path_dict: 148 | new_path_operations = path_dict[new_path].keys() 149 | path_operations = http_operations.keys() 150 | if len(set(path_operations).intersection( 151 | new_path_operations)) < 1: 152 | for http_method, operation_dict in http_operations.items(): 153 | operation_dict['parameters'] = operation_dict['parameters'] + query_param 154 | path_dict[new_path] = self.merge_dictionaries( 155 | http_operations, path_dict[new_path]) 156 | paths_to_delete.append(old_path) 157 | else: 158 | for http_method, operation_dict in http_operations.items(): 159 | operation_dict['parameters'].append(q_param) 160 | path_dict[new_path] = path_dict.pop(old_path) 161 | for path in paths_to_delete: 162 | del path_dict[path] -------------------------------------------------------------------------------- /lib/type_handler_common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import six 5 | from lib import utils 6 | 7 | 8 | class TypeHandlerCommon(): 9 | 10 | def __init__(self, show_unreleased_apis): 11 | self.show_unreleased_apis = show_unreleased_apis 12 | 13 | def visit_type_category( 14 | self, 15 | struct_type, 16 | new_prop, 17 | type_dict, 18 | structure_svc, 19 | enum_svc, 20 | ref_path): 21 | if isinstance(struct_type, dict): 22 | return self.visit_type_category_dict( 23 | struct_type, 24 | new_prop, 25 | type_dict, 26 | structure_svc, 27 | enum_svc, 28 | ref_path) 29 | if struct_type.category == 'BUILTIN': 30 | self.visit_builtin(struct_type.builtin_type, new_prop) 31 | elif struct_type.category == 'GENERIC': 32 | self.visit_generic( 33 | struct_type.generic_instantiation, 34 | new_prop, 35 | type_dict, 36 | structure_svc, 37 | enum_svc, 38 | ref_path) 39 | elif struct_type.category == 'USER_DEFINED': 40 | self.visit_user_defined( 41 | struct_type.user_defined_type, 42 | new_prop, 43 | type_dict, 44 | structure_svc, 45 | enum_svc, 46 | ref_path) 47 | 48 | def visit_type_category_dict( 49 | self, 50 | struct_type, 51 | new_prop, 52 | type_dict, 53 | structure_svc, 54 | enum_svc, 55 | ref_path): 56 | new_prop['required'] = True 57 | if struct_type['category'] == 'BUILTIN': 58 | self.visit_builtin(struct_type['builtin_type'], new_prop) 59 | elif struct_type['category'] == 'GENERIC': 60 | self.visit_generic( 61 | struct_type['generic_instantiation'], 62 | new_prop, 63 | type_dict, 64 | structure_svc, 65 | enum_svc, 66 | ref_path) 67 | elif struct_type['category'] == 'USER_DEFINED': 68 | self.visit_user_defined( 69 | struct_type['user_defined_type'], 70 | new_prop, 71 | type_dict, 72 | structure_svc, 73 | enum_svc, 74 | ref_path) 75 | 76 | def visit_builtin(self, builtin_type, new_prop): 77 | data_type, format_ = utils.metamodel_to_swagger_type_converter( 78 | builtin_type) 79 | if 'type' in new_prop and new_prop['type'] == 'array': 80 | item_obj = {'type': data_type} 81 | new_prop['items'] = item_obj 82 | if format_ is not None: 83 | item_obj['format'] = format_ 84 | else: 85 | new_prop['type'] = data_type 86 | if format_ is not None: 87 | new_prop['format'] = format_ 88 | 89 | def visit_generic( 90 | self, 91 | generic_instantiation, 92 | new_prop, 93 | type_dict, 94 | structure_svc, 95 | enum_svc, 96 | ref_path): 97 | pass 98 | 99 | def get_structure_info(self, struct_type, structure_svc): 100 | if self.show_unreleased_apis: 101 | return self.__get_structure_info(struct_type, structure_svc) 102 | else: 103 | return self.__get_structure_info_filtered(struct_type, structure_svc) 104 | 105 | def __get_structure_info(self, struct_type, structure_svc): 106 | try: 107 | structure_info = structure_svc.get(struct_type) 108 | if structure_info is None: 109 | utils.eprint( 110 | "Could not fetch structure info for " + 111 | struct_type) 112 | return structure_info 113 | except Exception as ex: 114 | utils.eprint("Error fetching structure info for " + struct_type) 115 | utils.eprint(ex) 116 | return None 117 | 118 | def __get_structure_info_filtered(self, struct_type, structure_svc): 119 | structure_info = self.__get_structure_info(struct_type, structure_svc) 120 | if structure_info is None or utils.is_filtered(structure_info.metadata): 121 | return None 122 | structure_info.fields = [field for field in structure_info.fields if not utils.is_filtered(field.metadata)] 123 | return structure_info 124 | 125 | def process_structure_info( 126 | self, 127 | type_name, 128 | structure_info, 129 | type_dict, 130 | structure_svc, 131 | enum_svc, 132 | ref_path): 133 | new_type = {'type': 'object', 'properties': {}} 134 | for field in structure_info.fields: 135 | newprop = {'description': field.documentation} 136 | if field.type.category == 'BUILTIN': 137 | self.visit_builtin(field.type.builtin_type, newprop) 138 | elif field.type.category == 'GENERIC': 139 | self.visit_generic( 140 | field.type.generic_instantiation, 141 | newprop, 142 | type_dict, 143 | structure_svc, 144 | enum_svc, 145 | ref_path) 146 | elif field.type.category == 'USER_DEFINED': 147 | self.visit_user_defined( 148 | field.type.user_defined_type, 149 | newprop, 150 | type_dict, 151 | structure_svc, 152 | enum_svc, 153 | ref_path) 154 | new_type['properties'].setdefault(self.get_fieldname(field), newprop) 155 | required = [] 156 | for property_name, property_value in six.iteritems( 157 | new_type['properties']): 158 | if 'required' not in property_value: 159 | required.append(property_name) 160 | elif property_value['required'] == 'true': 161 | required.append(property_name) 162 | if len(required) > 0: 163 | new_type['required'] = required 164 | type_dict[type_name] = new_type 165 | 166 | def get_fieldname(self, field): 167 | if hasattr(field, "metadata") and "SerializationName" in field.metadata: 168 | return field.metadata["SerializationName"].elements["value"].string_value 169 | else: 170 | return field.name 171 | 172 | def get_enum_info(self, type_name, enum_svc): 173 | if self.show_unreleased_apis: 174 | return self.__get_enum_info(type_name, enum_svc) 175 | else: 176 | return self.__get_enum_info_filtered(type_name, enum_svc) 177 | 178 | def __get_enum_info(self, type_name, enum_svc): 179 | try: 180 | enum_info = enum_svc.get(type_name) 181 | if enum_info is None: 182 | utils.eprint("Could not fetch enum info for " + type_name) 183 | return enum_info 184 | except Exception as exception: 185 | utils.eprint("Error fetching enum info for " + type_name) 186 | utils.eprint(exception) 187 | return None 188 | 189 | def __get_enum_info_filtered(self, type_name, enum_svc): 190 | enum_info = self.__get_enum_info(type_name, enum_svc) 191 | if enum_info is None or utils.is_filtered(enum_info.metadata): 192 | return None 193 | return enum_info 194 | 195 | def process_enum_info( 196 | self, 197 | type_name, 198 | enum_info, 199 | type_dict): 200 | enum_type = {'type': 'string', 'description': enum_info.documentation} 201 | if self.show_unreleased_apis: 202 | enum_type.setdefault('enum', [value.value for value in enum_info.values]) 203 | else: 204 | enum_type.setdefault( 205 | 'enum', [value.value for value in enum_info.values if not utils.is_filtered(value.metadata)]) 206 | type_dict[type_name] = enum_type 207 | 208 | def check_type( 209 | self, 210 | resource_type, 211 | type_name, 212 | type_dict, 213 | structure_svc, 214 | enum_svc, 215 | ref_path): 216 | if type_name in type_dict or utils.is_type_builtin(type_name): 217 | return 218 | if resource_type == 'com.vmware.vapi.structure': 219 | structure_info = self.get_structure_info(type_name, structure_svc) 220 | if structure_info is not None: 221 | # Mark it as visited to handle recursive definitions. (Type A 222 | # referring to Type A in one of the fields). 223 | type_dict[type_name] = {} 224 | self.process_structure_info( 225 | type_name, 226 | structure_info, 227 | type_dict, 228 | structure_svc, 229 | enum_svc, 230 | ref_path) 231 | else: 232 | enum_info = self.get_enum_info(type_name, enum_svc) 233 | if enum_info is not None: 234 | # Mark it as visited to handle recursive definitions. (Type A 235 | # referring to Type A in one of the fields). 236 | type_dict[type_name] = {} 237 | self.process_enum_info( 238 | type_name, enum_info, type_dict) 239 | 240 | def visit_user_defined( 241 | self, 242 | user_defined_type, 243 | newprop, 244 | type_dict, 245 | structure_svc, 246 | enum_svc, 247 | ref_path): 248 | if user_defined_type.resource_id is None: 249 | return 250 | if 'type' in newprop and newprop['type'] == 'array': 251 | item_obj = {'$ref': ref_path + user_defined_type.resource_id} 252 | newprop['items'] = item_obj 253 | # if not array, fill in type or ref 254 | else: 255 | newprop['$ref'] = ref_path + user_defined_type.resource_id 256 | 257 | self.check_type( 258 | user_defined_type.resource_type, 259 | user_defined_type.resource_id, 260 | type_dict, 261 | structure_svc, 262 | enum_svc, 263 | ref_path) 264 | -------------------------------------------------------------------------------- /lib/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import requests 5 | import json 6 | import sys 7 | import six 8 | import re 9 | from six.moves import http_client 10 | 11 | TAG_SEPARATOR = '/' 12 | CAMELCASE_SEPARATOR_LIST = [".", "_"] 13 | 14 | 15 | def eprint(*args, **kwargs): 16 | print(*args, file=sys.stderr, **kwargs) 17 | 18 | 19 | def get_json(url, verify=True): 20 | try: 21 | req = requests.get(url, verify=verify) 22 | except Exception as ex: 23 | eprint('Cannot Load %s - %s' % (url, req.content)) 24 | eprint(ex) 25 | return None 26 | if not req.ok: 27 | eprint('Cannot Load %s - %s' % (url, req.content)) 28 | return None 29 | if 'value' in req.json(): 30 | return req.json()['value'] 31 | return req.json() 32 | 33 | 34 | def write_json_data_to_file(file_name, json_data): 35 | """ 36 | Utility method used to write json file. 37 | """ 38 | with open(file_name, 'w+') as outfile: 39 | json.dump(json_data, outfile, indent=4) 40 | 41 | 42 | def load_description(): 43 | """ 44 | Loads description.properties into a dictionary. 45 | """ 46 | desc = { 47 | 'content': 'VMware vSphere\u00ae Content Library empowers vSphere Admins to effectively manage VM templates, ' 48 | 'vApps, ISO images and scripts with ease.', 49 | 'spbm': 'SPBM', 50 | 'vapi': 'vAPI is an extensible API Platform for modelling and delivering APIs/SDKs/CLIs.', 51 | 'vcenter': 'VMware vCenter Server provides a centralized platform for managing your VMware vSphere environments', 52 | 'appliance': 'The vCenter Server Appliance is a preconfigured Linux-based virtual machine' 53 | ' optimized for running vCenter Server and associated services.'} 54 | return desc 55 | 56 | 57 | def get_str_camel_case(string, *delimiters): 58 | delimiter_regex = '\\' + '|\\'.join(delimiters) 59 | words = [word[:1].upper() + word[1:] for word in re.split(delimiter_regex, string)] 60 | return ''.join(words) 61 | 62 | 63 | def is_filtered(metadata): 64 | if len(metadata) == 0: 65 | return False 66 | if 'TechPreview' in metadata: 67 | return False 68 | if 'Changing' in metadata or 'Proposed' in metadata: 69 | return True 70 | return False 71 | 72 | 73 | def recursive_ref_update(dict_obj, old, updated): 74 | for k, v in six.iteritems(dict_obj): 75 | if type(v) is str and v.endswith(old): 76 | dict_obj[k] = v.replace(old, updated) 77 | if type(v) is list: 78 | for element in v: 79 | if type(element) is dict: 80 | recursive_ref_update(element, old, updated) 81 | if type(v) is dict: 82 | recursive_ref_update(v, old, updated) 83 | 84 | 85 | def combine_dicts_with_list_values(extended, added): 86 | for k, v in added.items(): 87 | list_to_extend = extended.get(k, None) 88 | if list_to_extend is None: 89 | extended[k] = v 90 | else: 91 | list_to_extend.extend(v) 92 | extended[k] = list(set(list_to_extend)) 93 | 94 | 95 | def extract_path_parameters(params, url): 96 | """ 97 | Return list of field_infos which are path variables, another list of 98 | field_infos which are not path parameters and the url that eventually 99 | changed due to mismatching param names. 100 | An example of a URL that changes: 101 | /vcenter/resource-pool/{resource-pool} to 102 | /vcenter/resource-pool/{resource_pool} 103 | """ 104 | # Regex to look for {} placeholders with a group to match only the 105 | # parameter name 106 | re_path_param = re.compile('{(.+?)}') 107 | path_params = [] 108 | other_params = list(params) 109 | new_url = url 110 | for path_param_name_match in re_path_param.finditer(url): 111 | path_param_placeholder = path_param_name_match.group(1) 112 | path_param_info = None 113 | for param in other_params: 114 | if is_param_path_variable(param, path_param_placeholder): 115 | path_param_info = param 116 | if param.name != path_param_placeholder: 117 | new_url = new_url.replace( 118 | path_param_name_match.group(), '{' + param.name + '}') 119 | break 120 | if path_param_info is None: 121 | eprint( 122 | '%s parameter from %s is not found among the operation\'s parameters' % 123 | (path_param_placeholder, url)) 124 | else: 125 | path_params.append(path_param_info) 126 | other_params.remove(path_param_info) 127 | return path_params, other_params, new_url 128 | 129 | 130 | def is_param_path_variable(param, path_param_placeholder): 131 | if param.name == path_param_placeholder: 132 | return True 133 | if 'PathVariable' not in param.metadata: 134 | return False 135 | return param.metadata['PathVariable'].elements['value'].string_value == path_param_placeholder 136 | 137 | 138 | def build_path( 139 | service_name, 140 | method, 141 | path, 142 | documentation, 143 | parameters, 144 | operation_id, 145 | responses, 146 | consumes=None, 147 | produces=None): 148 | """ 149 | builds swagger path object 150 | :param service_name: name of service. ex com.vmware.vcenter.VM 151 | :param method: type of method. ex put, post, get, patch 152 | :param path: relative path to an individual endpoint 153 | :param documentation: api documentation 154 | :param parameters: input parameters for the api 155 | :param responses: response of the api 156 | :param consumes: expected media type format of api input 157 | :param produces: expected media type format of api output 158 | :return: swagger path object. 159 | """ 160 | path_obj = {} 161 | path_obj['tags'] = tags_from_service_name(service_name) 162 | if method is not None: 163 | path_obj['method'] = method 164 | if path is not None: 165 | path_obj['path'] = path 166 | if documentation is not None: 167 | path_obj['summary'] = documentation 168 | if parameters is not None: 169 | path_obj['parameters'] = parameters 170 | if responses is not None: 171 | path_obj['responses'] = responses 172 | 173 | # TODO - currently 'consumes' and 'produces are global and hardcoded; 174 | # Create a finer-grained approach, utilizing the checks below 175 | if consumes is not None: 176 | path_obj['consumes'] = consumes 177 | if produces is not None: 178 | path_obj['produces'] = produces 179 | 180 | if operation_id is not None: 181 | path_obj['operationId'] = operation_id 182 | return path_obj 183 | 184 | 185 | def remove_curly_braces(string_name): 186 | if '{' in string_name: 187 | string_name = string_name.replace('{', '') 188 | if '}' in string_name: 189 | string_name = string_name.replace('}', '') 190 | return string_name 191 | 192 | 193 | def tags_from_service_name(service_name): 194 | """ 195 | Generates the tags based on the service name. 196 | :param service_name: name of the service 197 | :return: a list of tags 198 | """ 199 | return [TAG_SEPARATOR.join(service_name.split('.')[3:])] 200 | 201 | 202 | def add_query_param(url, param): 203 | """ 204 | Rudimentary support for adding a query parameter to a url. 205 | Does nothing if the parameter is already there. 206 | :param url: the input url 207 | :param param: the parameter to add (in the form of key=value) 208 | :return: url with added param, ?param or ¶m at the end 209 | """ 210 | pre_param_symbol = '?' 211 | query_index = url.find('?') 212 | if query_index > -1: 213 | if query_index == len(url): 214 | pre_param_symbol = '' 215 | elif url[query_index + 1:].find(param) > -1: 216 | return url 217 | else: 218 | pre_param_symbol = '&' 219 | return url + pre_param_symbol + param 220 | 221 | 222 | def add_basic_auth(path_obj): 223 | """Add basic auth security requirement to paths requiring it.""" 224 | if path_obj['path'] == '/com/vmware/cis/session' and path_obj['method'] == 'post': 225 | path_obj['security'] = [{'basic_auth': []}] 226 | return path_obj 227 | 228 | 229 | def extract_body_parameters(params): 230 | body_params = [] 231 | other_params = [] 232 | for param in params: 233 | if 'Body' in param.metadata or 'BodyField' in param.metadata: 234 | body_params.append(param) 235 | else: 236 | other_params.append(param) 237 | return body_params, other_params 238 | 239 | 240 | def extract_query_parameters(params): 241 | query_params = [] 242 | other_params = [] 243 | for param in params: 244 | if 'Query' in param.metadata: 245 | query_params.append(param) 246 | else: 247 | other_params.append(param) 248 | return query_params, other_params 249 | 250 | 251 | def metamodel_to_swagger_type_converter(input_type): 252 | """ 253 | Converts API Metamodel type to their equivalent Swagger type. 254 | A tuple is returned. first value of tuple is main type. 255 | second value of tuple has 'format' information, if available. 256 | """ 257 | input_type = input_type.lower() 258 | if input_type == 'date_time': 259 | return 'string', 'date-time' 260 | if input_type == 'secret': 261 | return 'string', 'password' 262 | if input_type == 'any_error': 263 | return 'string', None 264 | if input_type == 'opaque': 265 | return 'object', None 266 | if input_type == 'dynamic_structure': 267 | return 'object', None 268 | if input_type == 'uri': 269 | return 'string', 'uri' 270 | if input_type == 'id': 271 | return 'string', None 272 | if input_type == 'long': 273 | return 'integer', 'int64' 274 | if input_type == 'double': 275 | return 'number', 'double' 276 | if input_type == 'binary': 277 | return 'string', 'binary' 278 | return input_type, None 279 | 280 | 281 | def is_type_builtin(type_): 282 | type_ = type_.lower() 283 | typeset = { 284 | 'binary', 285 | 'boolean', 286 | 'datetime', 287 | 'double', 288 | 'dynamicstructure', 289 | 'exception', 290 | 'id', 291 | 'long', 292 | 'opaque', 293 | 'secret', 294 | 'string', 295 | 'uri'} 296 | if type_ in typeset: 297 | return True 298 | return False 299 | 300 | 301 | def create_req_body_from_params_list(path_obj): 302 | # create request body section inside path object from parameter list 303 | parameters = path_obj['parameters'] 304 | if parameters: 305 | index = -1 306 | for i in range(len(parameters)): 307 | if '$ref' in parameters[i] and parameters[i]['$ref'].startswith('#/components/requestBodies/'): 308 | index = i 309 | break 310 | if index != -1: 311 | path_obj['requestBody'] = {'$ref': parameters[index]['$ref']} 312 | del parameters[index] 313 | 314 | 315 | class HttpErrorMap: 316 | """ 317 | Builds error_map which maps vapi errors to http status codes. 318 | """ 319 | 320 | def __init__(self, component_svc): 321 | self.component_svc = component_svc 322 | self.error_api_map = {} 323 | self.error_rest_map = {'com.vmware.vapi.std.errors.already_exists': http_client.BAD_REQUEST, 324 | 'com.vmware.vapi.std.errors.already_in_desired_state': http_client.BAD_REQUEST, 325 | 'com.vmware.vapi.std.errors.feature_in_use': http_client.BAD_REQUEST, 326 | 'com.vmware.vapi.std.errors.internal_server_error': http_client.INTERNAL_SERVER_ERROR, 327 | 'com.vmware.vapi.std.errors.invalid_argument': http_client.BAD_REQUEST, 328 | 'com.vmware.vapi.std.errors.invalid_element_configuration': http_client.BAD_REQUEST, 329 | 'com.vmware.vapi.std.errors.invalid_element_type': http_client.BAD_REQUEST, 330 | 'com.vmware.vapi.std.errors.invalid_request': http_client.BAD_REQUEST, 331 | 'com.vmware.vapi.std.errors.not_found': http_client.NOT_FOUND, 332 | 'com.vmware.vapi.std.errors.operation_not_found': http_client.NOT_FOUND, 333 | 'com.vmware.vapi.std.errors.not_allowed_in_current_state': http_client.BAD_REQUEST, 334 | 'com.vmware.vapi.std.errors.resource_busy': http_client.BAD_REQUEST, 335 | 'com.vmware.vapi.std.errors.resource_in_use': http_client.BAD_REQUEST, 336 | 'com.vmware.vapi.std.errors.resource_inaccessible': http_client.BAD_REQUEST, 337 | 'com.vmware.vapi.std.errors.service_unavailable': http_client.SERVICE_UNAVAILABLE, 338 | 'com.vmware.vapi.std.errors.timed_out': http_client.GATEWAY_TIMEOUT, 339 | 'com.vmware.vapi.std.errors.unable_to_allocate_resource': http_client.BAD_REQUEST, 340 | 'com.vmware.vapi.std.errors.unauthenticated': http_client.UNAUTHORIZED, 341 | 'com.vmware.vapi.std.errors.unauthorized': http_client.FORBIDDEN, 342 | 'com.vmware.vapi.std.errors.unexpected_input': http_client.BAD_REQUEST, 343 | 'com.vmware.vapi.std.errors.unsupported': http_client.BAD_REQUEST, 344 | 'com.vmware.vapi.std.errors.error': http_client.BAD_REQUEST, 345 | 'com.vmware.vapi.std.errors.concurrent_change': http_client.BAD_REQUEST, 346 | 'com.vmware.vapi.std.errors.canceled': http_client.BAD_REQUEST, 347 | 'com.vmware.vapi.std.errors.unverified_peer': http_client.BAD_REQUEST} 348 | 349 | structures = self.component_svc.get('com.vmware.vapi').info.packages['com.vmware.vapi.std.errors'].structures 350 | for structure in structures: 351 | try: 352 | if structures[structure].metadata['Response'] is not None: 353 | code = structures[structure].metadata['Response'].elements['code'].string_value 354 | self.error_api_map[structure] = int(code) 355 | except KeyError: 356 | print(structure + " :: is does not have an Error Code") 357 | -------------------------------------------------------------------------------- /test_authentication_metadata_processing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import unittest 5 | 6 | from lib.authentication_metadata_processing import AuthenticationComponent, AuthenticationDictNavigator 7 | from lib.authentication_metadata_processing import AuthenticationComponentBuilder 8 | from unittest import mock 9 | 10 | class TestAuthenticationComponent(unittest.TestCase): 11 | 12 | session_id_auth_info_mock = mock.Mock() 13 | session_id_auth_info_mock.scheme = "session_id" 14 | token_auth_info_mock = mock.Mock() 15 | token_auth_info_mock.scheme = "token" 16 | oauth_auth_info_mock = mock.Mock() 17 | oauth_auth_info_mock.scheme = "oauth" 18 | operation_info_mock = mock.Mock() 19 | operation_info_mock.schemes = [session_id_auth_info_mock, token_auth_info_mock] 20 | service_info_mock = mock.Mock() 21 | service_info_mock.schemes = [token_auth_info_mock, oauth_auth_info_mock] 22 | service_info_mock.operations = {"create": operation_info_mock} 23 | package_info_mock = mock.Mock() 24 | package_info_mock.services = {"com.vmware.cis.session": service_info_mock} 25 | package_info_mock.schemes = [oauth_auth_info_mock, session_id_auth_info_mock] 26 | 27 | def test_authentication_component(self): 28 | package_level_auth_component = AuthenticationComponent() 29 | package_level_auth_component.add_schemes(["basic_auth"]) 30 | 31 | self.assertEqual({"basic_auth"}, package_level_auth_component.get_schemes_set()) 32 | 33 | service_level_auth_component = AuthenticationComponent() 34 | service_level_auth_component.add_schemes(["token"]) 35 | package_level_auth_component.add_subcomponent(service_level_auth_component, "my.token.auth.service") 36 | 37 | subcomponents_dict = package_level_auth_component.get_subcomponents_dict() 38 | self.assertEqual({"token"}, subcomponents_dict.get("my.token.auth.service").get_schemes_set()) 39 | 40 | operation_level_auth_component = AuthenticationComponent() 41 | operation_level_auth_component.add_schemes(["saml_token"]) 42 | service_level_auth_component_updated = AuthenticationComponent() 43 | service_level_auth_component_updated.add_schemes({"token", "oauth2"}) 44 | service_level_auth_component_updated.add_subcomponent(operation_level_auth_component, 45 | "my.token.auth.service.list") 46 | package_level_auth_component.add_subcomponent(service_level_auth_component_updated, 47 | "my.token.auth.service") 48 | 49 | self.assertEqual({"token", "oauth2"}, subcomponents_dict.get("my.token.auth.service").get_schemes_set()) 50 | 51 | operation_level_auth_component_updated = AuthenticationComponent() 52 | operation_level_auth_component_updated.add_schemes(["session_id"]) 53 | service_level_auth_component_updated.add_subcomponent(operation_level_auth_component_updated, 54 | "my.token.auth.service.list") 55 | service_level_auth_component_updated.add_subcomponent(operation_level_auth_component_updated, 56 | "my.token.auth.service.create") 57 | package_level_auth_component.add_subcomponent(service_level_auth_component_updated, 58 | "my.token.auth.service") 59 | 60 | subcomponents_dict = package_level_auth_component.get_subcomponents_dict() 61 | operation_level_subcomponents_dict = subcomponents_dict.get("my.token.auth.service").get_subcomponents_dict() 62 | self.assertEqual(2, len(operation_level_subcomponents_dict)) 63 | 64 | self.assertEqual({"session_id", "saml_token"}, 65 | operation_level_subcomponents_dict.get("my.token.auth.service.list").get_schemes_set()) 66 | self.assertEqual({"session_id"}, 67 | operation_level_subcomponents_dict.get("my.token.auth.service.create").get_schemes_set()) 68 | 69 | 70 | found_component = package_level_auth_component.recursive_search_for_component("my.token.auth.service.list") 71 | self.assertEqual({"session_id", "saml_token"}, found_component.get_schemes_set()) 72 | found_component = package_level_auth_component.recursive_search_for_component("my.token.auth.service") 73 | self.assertEqual({"token", "oauth2"}, found_component.get_schemes_set()) 74 | 75 | 76 | def test_authentication_component_builder(self): 77 | package_component = AuthenticationComponentBuilder.build_package_level_component(self.package_info_mock) 78 | self.assertEqual({"oauth", "session_id"}, package_component.get_schemes_set()) 79 | service_component = package_component.get_subcomponents_dict()["com.vmware.cis.session"] 80 | self.assertEqual({"oauth", "token"}, service_component.get_schemes_set()) 81 | operation_component = service_component.get_subcomponents_dict()["create"] 82 | self.assertEqual({"session_id", "token"}, operation_component.get_schemes_set()) 83 | 84 | 85 | def test_authentication_dict_navigator(self): 86 | package_component = AuthenticationComponentBuilder.build_package_level_component(self.package_info_mock) 87 | auth_dict = {"com.vmware.cis": package_component} 88 | navigator = AuthenticationDictNavigator(auth_dict) 89 | 90 | # 1. Existing package, existing service, existing operation 91 | self.assertEqual({"session_id", "token"}, navigator.find_schemes_set("create", "com.vmware.cis.session", "cis")) 92 | 93 | # 2. Existing package, existing service, non-existing operation 94 | self.assertEqual({"oauth", "token"}, navigator.find_schemes_set("update", "com.vmware.cis.session", "cis")) 95 | 96 | # 3. Existing package, non-existing service 97 | self.assertEqual({"oauth", "session_id"}, navigator.find_schemes_set("update", "com.vmware.cis.tasks", "cis")) 98 | 99 | # 4. Service name equal to package name 100 | self.assertEqual({"oauth", "session_id"}, navigator.find_schemes_set("update", "com.vmware.cis", "cis")) 101 | 102 | # 5. Non-existing package 103 | self.assertEqual(None, navigator.find_schemes_set("update", "com.vmware.sample", "cis")) 104 | 105 | if __name__ == '__main__': 106 | unittest.main() 107 | -------------------------------------------------------------------------------- /test_output_handling.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import unittest 5 | 6 | from lib.file_output_handler import SpecificationDictsMerger 7 | 8 | 9 | class TestSpecificationDictsMerger(unittest.TestCase): 10 | 11 | def test_merge_api_rest_dicts(self): 12 | rest_path_dict = {"/rest/com/vmware/vcenter/ovf/library_item": 13 | {"post": { 14 | "tags": [ 15 | "ovf/library_item" 16 | ], 17 | "parameters": [ 18 | { 19 | "in": "body", 20 | "name": "request_body", 21 | "required": True, 22 | "schema": { 23 | "$ref": "#/definitions/com.vmware.vcenter.ovf.library_item_create" 24 | } 25 | } 26 | ], 27 | "responses": { 28 | 200: { 29 | "schema": { 30 | "$ref": "#/definitions/com.vmware.vcenter.ovf.library_item.create_resp" 31 | } 32 | }, 33 | 400: { 34 | "description": "if the specified virtual machine or virtual appliance is busy.", 35 | "schema": { 36 | "$ref": "#/definitions/com.vmware.vapi.std.errors.resource_busy_error" 37 | } 38 | }, 39 | 404: { 40 | "description": "if the virtual machine or virtual appliance specified by {@param.name source} does not exist.", 41 | "schema": { 42 | "$ref": "#/definitions/com.vmware.vapi.std.errors.not_found_error" 43 | } 44 | } 45 | }, 46 | "operationId": "create" 47 | } 48 | }, 49 | "/rest/com/vmware/vcenter/ovf/import_flag": { 50 | "get": { 51 | "tags": [ 52 | "ovf/import_flag" 53 | ], 54 | "summary": "Returns information about the import flags supported by the deployment platform.

The supported flags are:

LAX
Lax mode parsing of the OVF descriptor.

Future server versions might support additional flags.", 55 | "parameters": [ 56 | { 57 | "type": "string", 58 | "in": "query", 59 | "name": "rp", 60 | "description": "The identifier of resource pool target for retrieving the import flag(s).", 61 | "required": True 62 | } 63 | ], 64 | "responses": { 65 | 200: { 66 | "description": "A {@term list} of supported import flags.", 67 | "schema": { 68 | "$ref": "#/definitions/com.vmware.vcenter.ovf.import_flag.list_resp" 69 | } 70 | }, 71 | 404: { 72 | "description": "If the resource pool associated with {@param.name rp} does not exist.", 73 | "schema": { 74 | "$ref": "#/definitions/com.vmware.vapi.std.errors.not_found_error" 75 | } 76 | } 77 | }, 78 | "operationId": "list" 79 | } 80 | } 81 | } 82 | rest_type_dict = {"com.vmware.vcenter.ovf.import_flag.list_resp": { 83 | "type": "object", 84 | "properties": { 85 | "value": { 86 | "type": "array", 87 | "items": { 88 | "$ref": "#/definitions/com.vmware.vcenter.ovf.import_flag.info" 89 | } 90 | } 91 | }, 92 | "required": [ 93 | "value" 94 | ] 95 | }} 96 | 97 | # Use copies rather than references to the same dict 98 | api_path_dict = { 99 | "/api/com/vmware/vcenter/ovf/library_item": {"post": { 100 | "tags": [ 101 | "ovf/library_item" 102 | ], 103 | "parameters": [ 104 | { 105 | "in": "body", 106 | "name": "request_body", 107 | "required": True, 108 | "schema": { 109 | "$ref": "#/definitions/com.vmware.vcenter.ovf.library_item_create" 110 | } 111 | } 112 | ], 113 | "responses": { 114 | 200: { 115 | "schema": { 116 | "$ref": "#/definitions/com.vmware.vcenter.ovf.library_item.create_resp" 117 | } 118 | }, 119 | 400: { 120 | "description": "if the specified virtual machine or virtual appliance is busy.", 121 | "schema": { 122 | "$ref": "#/definitions/com.vmware.vapi.std.errors.resource_busy_error" 123 | } 124 | }, 125 | 404: { 126 | "description": "if the virtual machine or virtual appliance specified by {@param.name source} does not exist.", 127 | "schema": { 128 | "$ref": "#/definitions/com.vmware.vapi.std.errors.not_found_error" 129 | } 130 | } 131 | }, 132 | "operationId": "create" 133 | } 134 | }, 135 | "/api/com/vmware/vcenter/ovf/import_flag": { 136 | "get": { 137 | "tags": [ 138 | "ovf/import_flag" 139 | ], 140 | "summary": "Returns information about the import flags supported by the deployment platform.

The supported flags are:

LAX
Lax mode parsing of the OVF descriptor.

Future server versions might support additional flags.", 141 | "parameters": [ 142 | { 143 | "type": "string", 144 | "in": "query", 145 | "name": "rp", 146 | "description": "The identifier of resource pool target for retrieving the import flag(s).", 147 | "required": True 148 | } 149 | ], 150 | "responses": { 151 | 200: { 152 | "description": "A {@term list} of supported import flags.", 153 | "schema": { 154 | "$ref": "#/definitions/com.vmware.vcenter.ovf.import_flag.list_resp" 155 | } 156 | }, 157 | 404: { 158 | "description": "If the resource pool associated with {@param.name rp} does not exist.", 159 | "schema": { 160 | "$ref": "#/definitions/com.vmware.vapi.std.errors.not_found_error" 161 | } 162 | } 163 | }, 164 | "operationId": "list" 165 | } 166 | }, 167 | "/api/com/vmware/vcenter/ovf/export_flag": {} 168 | } 169 | api_type_dict = {"ComVmwareVcenterOvfImportFlag": { 170 | "type": "object", 171 | "properties": { 172 | "value": { 173 | "type": "array", 174 | "items": { 175 | "$ref": "#/definitions/com.vmware.vcenter.ovf.import_flag.info" 176 | } 177 | } 178 | }, 179 | "required": [ 180 | "value" 181 | ] 182 | }} 183 | 184 | rest_spec = {"vcenter": (dict(rest_path_dict), dict(rest_type_dict)), 185 | "appliance": ({}, {})} 186 | api_spec = {"vcenter": (dict(api_path_dict), dict(api_type_dict)), 187 | "cis": ({}, {})} 188 | 189 | merger = SpecificationDictsMerger(dict(rest_spec), dict(api_spec)) 190 | merged = merger.merge_api_rest_dicts() 191 | self.assertTrue("vcenter" in merged and "cis" in merged and "appliance" in merged) 192 | self.assertEqual(len(merged["vcenter"][0]), 5) 193 | self.assertEqual(len(merged["vcenter"][1]), 2) 194 | api_def_type = merged["vcenter"][0] \ 195 | .get("/api/com/vmware/vcenter/ovf/import_flag") \ 196 | .get("get") \ 197 | .get("responses") \ 198 | .get(200) \ 199 | .get("schema") \ 200 | .get("$ref") 201 | self.assertEqual(api_def_type, "#/definitions/com.vmware.vcenter.ovf.import_flag.list_resp") 202 | 203 | 204 | if __name__ == '__main__': 205 | unittest.main() 206 | -------------------------------------------------------------------------------- /test_rest_deprecation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import unittest 5 | 6 | from lib.rest_endpoint.rest_deprecation_handler import RestDeprecationHandler 7 | 8 | 9 | class TestRestDeprecationHandler(unittest.TestCase): 10 | 11 | sample_package = "vcenter" 12 | sample_service = "com.vmware.vcenter" 13 | sample_operation = "list" 14 | sample_method = "get" 15 | sample_path = "/rest/vcenter/vm" 16 | replacement_dict = {sample_service: {sample_operation: (sample_method, sample_path)}} 17 | rest_deprecation_handler = RestDeprecationHandler(replacement_dict) 18 | 19 | def test_rest_deprecation(self): 20 | path_obj = {"operationId": self.sample_operation, "method": self.sample_method} 21 | self.rest_deprecation_handler.add_deprecation_information(path_obj, self.sample_package, self.sample_service) 22 | 23 | self.assertEqual(path_obj['deprecated'], True) 24 | self.assertEqual(path_obj["x-vmw-deprecated"]["replacement"], "api_vcenter.json#/paths/~1api~1rest~1vcenter~1vm/get") 25 | 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | import unittest 5 | 6 | from lib import utils 7 | 8 | 9 | class TestUtils(unittest.TestCase): 10 | 11 | def test_combine_dicts_with_list_values(self): 12 | extended = {"1": ["1", "2", "3"], 13 | "2": ["4", "5", "6"]} 14 | added = {"3": ["8", "9"], 15 | "2": ["4", "5", "6", "7"]} 16 | 17 | utils.combine_dicts_with_list_values(extended, added) 18 | # Note after combining lists, the original order is not preserved 19 | extended.get("1").sort() 20 | extended.get("2").sort() 21 | extended.get("3").sort() 22 | self.assertEqual(extended.get("1"), ["1", "2", "3"]) 23 | self.assertEqual(extended.get("2"), ["4", "5", "6", "7"]) 24 | self.assertEqual(extended.get("3"), ["8", "9"]) 25 | 26 | def test_recursive_ref_update(self): 27 | sample_dict = {'get': {'tags': ['vm/compute/policies'], 28 | 'summary': 'Returns information about the compliance of a virtual machine with a compute policy in VMware Cloud on AWS. Usage beyond VMware Cloud on AWS is not supported. Warning: This operation is available as Technology Preview. These are early access APIs provided to test, automate and provide feedback on the feature. Since this can change based on feedback, VMware does not guarantee backwards compatibility and recommends against using them in production environments. Some Technology Preview APIs might only be applicable to specific environments.', 29 | 'parameters': [{'type': 'string', 'required': True, 'in': 'path', 'name': 'vm', 30 | 'description': 'Identifier of the virtual machine to query the status for.'}, 31 | {'type': 'string', 'required': True, 'in': 'path', 'name': 'policy', 32 | 'description': 'Identifier of the policy to query the status for.'}], 33 | 'responses': { 34 | 200: { 35 | 'description': 'Information about the compliance of the specified virtual machine with the specified compute policy.', 36 | 'schema': {'$ref': '#/definitions/com.vmware.vcenter.vm.compute.policies.info'}}, 37 | 404: { 38 | 'description': 'if a virtual machine with the given identifier does not exist, or if a policy with the given identifier does not exist.', 39 | 'schema': {'$ref': '#/definitions/com.vmware.vapi.std.errors.not_found'}}, 40 | 403: {'description': "if the user doesn't have the required privileges.", 41 | 'schema': {'$ref': '#/definitions/com.vmware.vapi.std.errors.unauthorized'}}, 42 | 401: {'description': "if the user doesn't have the required privileges.", 43 | 'schema': {'$ref': '#/definitions/com.vmware.vapi.std.errors.unauthorized'}} 44 | }, 45 | 46 | 'operationId': 'get'}} 47 | old = '#/definitions/com.vmware.vcenter.vm.compute.policies.info' 48 | updated = '#/definitions/my.new.definition.resp' 49 | utils.recursive_ref_update(sample_dict, old, updated) 50 | self.assertEqual(sample_dict.get('get').get('responses').get(200).get('schema').get('$ref'), updated) 51 | old = '#/definitions/com.vmware.vapi.std.errors.unauthorized' 52 | updated = '#/definitions/my.new.definition.resp' 53 | utils.recursive_ref_update(sample_dict, old, updated) 54 | self.assertEqual(sample_dict.get('get').get('responses').get(401).get('schema').get('$ref'), updated) 55 | self.assertEqual(sample_dict.get('get').get('responses').get(403).get('schema').get('$ref'), updated) 56 | old = 'Identifier of the policy to query the status for.' 57 | updated = 'Some sample text...' 58 | utils.recursive_ref_update(sample_dict, old, updated) 59 | self.assertEqual(sample_dict.get('get').get('parameters')[1].get('description'), updated) 60 | 61 | def test_get_str_camel_case(self): 62 | string = "vapi.std_localizable_message" 63 | expected = "VapiStdLocalizableMessage" 64 | self.assertEqual(expected, utils.get_str_camel_case(string, *utils.CAMELCASE_SEPARATOR_LIST)) 65 | 66 | string = 'com.vmware.package.mock-1' 67 | expected = "ComVmwarePackageMock-1" 68 | self.assertEqual(expected, utils.get_str_camel_case(string, *utils.CAMELCASE_SEPARATOR_LIST)) 69 | 70 | string = "ComVmwarePackageMock1" 71 | expected = "ComVmwarePackageMock1" 72 | self.assertEqual(expected, utils.get_str_camel_case(string, *utils.CAMELCASE_SEPARATOR_LIST)) 73 | 74 | if __name__ == '__main__': 75 | unittest.main() 76 | -------------------------------------------------------------------------------- /vmsgen.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2020 VMware, Inc. 2 | # SPDX-License-Identifier: MIT 3 | 4 | ''' 5 | This script uses metamodel apis and rest navigation to generate openapi json files 6 | for apis available on vcenter. 7 | ''' 8 | from __future__ import print_function 9 | 10 | from concurrent import futures 11 | 12 | from lib import RestMetadataProcessor, authentication_metadata_processing 13 | from lib import ApiMetadataProcessor 14 | from lib import dictionary_processing as dict_processing 15 | from lib import establish_connection as connection 16 | from lib import utils 17 | from vmware.vapi.core import ApplicationContext 18 | from vmware.vapi.lib.constants import SHOW_UNRELEASED_APIS 19 | from vmware.vapi.lib.connect import get_requests_connector 20 | import timeit 21 | import warnings 22 | import requests 23 | import six 24 | 25 | from lib.authentication_metadata_processing import AuthenticationDictNavigator 26 | from lib.file_output_handler import FileOutputHandler 27 | from lib.rest_endpoint.rest_deprecation_handler import RestDeprecationHandler 28 | from lib.rest_endpoint.rest_navigation_handler import RestNavigationHandler 29 | 30 | warnings.filterwarnings("ignore") 31 | 32 | 33 | GENERATE_UNIQUE_OP_IDS = False 34 | GENERATE_METAMODEL = False 35 | API_SERVER_HOST = '' 36 | TAG_SEPARATOR = '/' 37 | SPECIFICATION = '3' 38 | DEPRECATE_REST = False 39 | 40 | def main(): 41 | # Get user input. 42 | metadata_api_url, \ 43 | rest_navigation_url, \ 44 | output_dir, \ 45 | verify, \ 46 | show_unreleased_apis, \ 47 | GENERATE_METAMODEL, \ 48 | SPECIFICATION, \ 49 | GENERATE_UNIQUE_OP_IDS, \ 50 | TAG_SEPARATOR, \ 51 | DEPRECATE_REST,\ 52 | fetch_auth_metadata,\ 53 | auto_rest_services = connection.get_input_params() 54 | # Maps enumeration id to enumeration info 55 | enumeration_dict = {} 56 | # Maps structure_id to structure_info 57 | structure_dict = {} 58 | # Maps service_id to service_info 59 | service_dict = {} 60 | # Maps service url to service id 61 | service_urls_map = {} 62 | 63 | rest_navigation_handler = RestNavigationHandler(rest_navigation_url) 64 | 65 | start = timeit.default_timer() 66 | print('Trying to connect ' + metadata_api_url) 67 | session = requests.session() 68 | session.verify = False 69 | connector = get_requests_connector(session, url=metadata_api_url) 70 | 71 | if show_unreleased_apis: 72 | connector.set_application_context( 73 | ApplicationContext({SHOW_UNRELEASED_APIS: "True"})) 74 | print('Connected to ' + metadata_api_url) 75 | component_svc = connection.get_component_service(connector) 76 | 77 | auth_navigator = None 78 | if fetch_auth_metadata: 79 | # Fetch authentication metadata and initialize the authentication data navigator 80 | auth_component_svc = connection.get_authentication_component_service(connector) 81 | auth_dict = authentication_metadata_processing.get_authentication_dict(auth_component_svc) 82 | auth_navigator = AuthenticationDictNavigator(auth_dict) 83 | 84 | dict_processing.populate_dicts( 85 | component_svc, 86 | enumeration_dict, 87 | structure_dict, 88 | service_dict, 89 | service_urls_map, 90 | rest_navigation_url, 91 | GENERATE_METAMODEL) 92 | 93 | http_error_map = utils.HttpErrorMap(component_svc) 94 | 95 | deprecation_handler = None 96 | 97 | # package_dict_api holds list of all service urls which come under /api 98 | # package_dict_deprecated holds a list of all service urls which come under /rest, but are 99 | # deprecated with /api 100 | # replacement_dict contains information about the deprecated /rest to /api mappings 101 | package_dict_api, package_dict, package_dict_deprecated, replacement_dict = dict_processing.add_service_urls_using_metamodel( 102 | service_urls_map, service_dict, rest_navigation_handler, auto_rest_services, DEPRECATE_REST) 103 | 104 | 105 | utils.combine_dicts_with_list_values(package_dict, package_dict_deprecated) 106 | if DEPRECATE_REST: 107 | deprecation_handler = RestDeprecationHandler(replacement_dict) 108 | 109 | rest = RestMetadataProcessor() 110 | api = ApiMetadataProcessor() 111 | 112 | rest_package_spec_dict = {} 113 | api_package_spec_dict = {} 114 | 115 | with futures.ThreadPoolExecutor() as executor: 116 | rest_package_future_dict = {package: executor.submit( 117 | rest.get_path_and_type_dicts, 118 | package, 119 | service_urls, 120 | structure_dict, 121 | enumeration_dict, 122 | service_dict, 123 | service_urls_map, 124 | http_error_map, 125 | rest_navigation_handler, 126 | show_unreleased_apis, 127 | SPECIFICATION, 128 | auth_navigator, 129 | deprecation_handler) for package, service_urls in 130 | six.iteritems(package_dict) 131 | } 132 | 133 | api_package_future_dict = {package: executor.submit( 134 | api.get_path_and_type_dicts, 135 | package, 136 | service_urls, 137 | structure_dict, 138 | enumeration_dict, 139 | service_dict, 140 | service_urls_map, 141 | http_error_map, 142 | show_unreleased_apis, 143 | SPECIFICATION, 144 | auth_navigator) for package, service_urls in 145 | six.iteritems(package_dict_api) 146 | } 147 | 148 | rest_package_spec_dict = {package: future.result() for package, future in 149 | six.iteritems(rest_package_future_dict)} 150 | api_package_spec_dict = {package: future.result() for package, future in 151 | six.iteritems(api_package_future_dict)} 152 | 153 | file_handler = FileOutputHandler(rest_package_spec_dict, 154 | api_package_spec_dict, 155 | output_dir, 156 | GENERATE_UNIQUE_OP_IDS, 157 | SPECIFICATION) 158 | file_handler.output_files() 159 | 160 | stop = timeit.default_timer() 161 | print('Generated swagger files at ' + output_dir + ' for ' + 162 | metadata_api_url + ' in ' + str(stop - start) + ' seconds') 163 | 164 | 165 | if __name__ == '__main__': 166 | main() 167 | --------------------------------------------------------------------------------