├── .coveragerc ├── .gitignore ├── .gitreview ├── .mailmap ├── .pre-commit-config.yaml ├── .stestr.conf ├── .zuul.yaml ├── CONTRIBUTING.rst ├── HACKING.rst ├── LICENSE ├── README.rst ├── RELEASING.rst ├── TESTS.rst ├── doc ├── requirements.txt └── source │ ├── conf.py │ ├── contributing.rst │ ├── http-status.yaml │ ├── index.rst │ ├── installation.rst │ └── usage.rst ├── os_api_ref ├── __init__.py ├── assets │ ├── api-site.css │ ├── api-site.js │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── http_codes.py └── tests │ ├── __init__.py │ ├── base.py │ ├── examples │ ├── basic │ │ ├── conf.py │ │ ├── index.rst │ │ ├── parameters.yaml │ │ └── status.yaml │ ├── microversions │ │ ├── conf.py │ │ ├── index.rst │ │ └── parameters.yaml │ └── warnings │ │ ├── conf.py │ │ ├── empty_parameters_file.yaml │ │ ├── index.rst │ │ └── parameters.yaml │ ├── test_basic_example.py │ ├── test_microversions.py │ ├── test_os_api_ref.py │ └── test_warnings.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = os_api_ref 4 | omit = os_api_ref/openstack/* 5 | 6 | [report] 7 | ignore_errors = True 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg* 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | cover/ 26 | .coverage* 27 | !.coveragerc 28 | .tox 29 | nosetests.xml 30 | .testrepository 31 | .venv 32 | .stestr/ 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | doc/build 48 | 49 | # pbr generates these 50 | AUTHORS 51 | ChangeLog 52 | 53 | # Editors 54 | *~ 55 | .*.swp 56 | .*sw? 57 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/os-api-ref.git 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Format is: 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | # Replaces or checks mixed line ending 7 | - id: mixed-line-ending 8 | args: ['--fix', 'lf'] 9 | exclude: '.*\.(svg)$' 10 | # Forbid files which have a UTF-8 byte-order marker 11 | - id: check-byte-order-marker 12 | # Checks that non-binary executables have a proper shebang 13 | - id: check-executables-have-shebangs 14 | # Check for files that contain merge conflict strings. 15 | - id: check-merge-conflict 16 | # Check for debugger imports and py37+ breakpoint() 17 | # calls in python source 18 | - id: debug-statements 19 | - id: check-yaml 20 | files: .*\.(yaml|yml)$ 21 | - repo: https://opendev.org/openstack/hacking 22 | rev: 7.0.0 23 | hooks: 24 | - id: hacking 25 | additional_dependencies: [] 26 | - repo: https://github.com/asottile/pyupgrade 27 | rev: v3.18.0 28 | hooks: 29 | - id: pyupgrade 30 | args: [--py3-only] 31 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=${OS_TEST_PATH:-./os_api_ref/tests} 3 | top_dir=./ -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - job: 2 | name: os-api-ref-nova-src 3 | parent: build-openstack-api-ref 4 | description: | 5 | Run the api-ref job against nova with proposed os-api-ref change. 6 | vars: 7 | zuul_work_dir: src/opendev.org/openstack/nova 8 | bindep_dir: src/opendev.org/openstack/nova 9 | required-projects: 10 | - openstack/nova 11 | - openstack/os-api-ref 12 | 13 | - project: 14 | templates: 15 | - openstack-python3-jobs 16 | - check-requirements 17 | - publish-openstack-docs-pti 18 | check: 19 | jobs: 20 | - os-api-ref-nova-src 21 | gate: 22 | jobs: 23 | - os-api-ref-nova-src 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of OpenStack, you must 2 | follow the steps in this page: 3 | 4 | https://docs.openstack.org/infra/manual/developers.html 5 | 6 | If you already have a good understanding of how the system works and your 7 | OpenStack accounts are set up, you can skip to the development workflow 8 | section of this documentation to learn how changes to OpenStack should be 9 | submitted for review via the Gerrit tool: 10 | 11 | https://docs.openstack.org/infra/manual/developers.html#development-workflow 12 | 13 | Pull requests submitted through GitHub will be ignored. 14 | 15 | Bugs should be filed on Launchpad, not GitHub: 16 | 17 | https://bugs.launchpad.net/openstack-doc-tools 18 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | os-api-ref Style Commandments 2 | =============================================== 3 | 4 | Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Team and repository tags 3 | ======================== 4 | 5 | .. image:: https://governance.openstack.org/tc/badges/os-api-ref.svg 6 | :target: https://governance.openstack.org/tc/reference/tags/index.html 7 | 8 | .. Change things from this point on 9 | 10 | os-api-ref 11 | ========== 12 | 13 | Sphinx Extensions to support API reference sites in OpenStack 14 | 15 | This project is a collection of sphinx stanzas that assist in building 16 | an API Reference site for an OpenStack project in RST. RST is great 17 | for unstructured English, but displaying semi structured (and 18 | repetitive) data in tables is not its strength. This provides tooling 19 | to insert semi-structured data describing request and response 20 | parameters and status or error messages, and turn those into nice tables. 21 | 22 | The project also includes a set of styling (and javascript) that is 23 | expected to layer on top of a Sphinx theme base. This addition 24 | provides a nice set of collapsing sections for REST methods and 25 | javascript controls to expand / collapse all sections. 26 | 27 | Features 28 | -------- 29 | 30 | * Sphinx stanza ``rest_method`` describing the method and resource for a REST 31 | API call. Lets authors write simply and also gives readers a clean way to 32 | scan all methods then click a button to get more information about a method. 33 | * Sphinx stanza ``rest_parameters`` used to insert semi-structured data into 34 | the RST files describing the parameters users can send with the request. The 35 | stanza points to a structured YAML file, ``parameters.yaml``. 36 | * Sphinx stanza ``rest_status_code`` used to insert pointers to error or status 37 | codes returned by the service. Points to a structured YAML file, 38 | ``http_codes.yaml``. 39 | 40 | TODO 41 | ---- 42 | 43 | A list, in no particular order, of things we should do in this 44 | project. If you would like to contribute to any of these please show 45 | up in ``#openstack-dev`` on IRC and ask for ``sdague`` or ``mugsie`` 46 | to discuss or send an email to the openstack-discuss@lists.openstack.org list 47 | with [api] in the subject line. 48 | 49 | * Enhance documentation with more examples and best practices 50 | * Testing for the code 51 | * ``max_microversion`` parameter support - so that we automatically 52 | tag parameters that have been removed 53 | * Make a microversion selector, so that you can get a version of the api-ref 54 | that hides all microversion elements beyond your selected version 55 | (this one is going to be a bit of complex javascript), in progress. 56 | 57 | Potential ideas 58 | ~~~~~~~~~~~~~~~ 59 | 60 | These aren't even quite todos, but just ideas about things that might 61 | be useful. 62 | 63 | * ``.. literalinclude`` is good for API samples files to be included, 64 | but should we have more markup that includes the full ``REST /URL`` 65 | as well. 66 | 67 | 68 | Sphinx stanzas 69 | -------------- 70 | 71 | **rest_method**: Enter the REST method, such as GET, PUT, POST, DELETE, 72 | followed by the resource (not including an endpoint) for the call. For 73 | example:: 74 | 75 | .. rest_method:: PUT /v2/images/{image_id}/file 76 | 77 | **rest_parameters**: Enter a reference to a ``parameters.yaml`` file and 78 | indicate which parameter you want to document. For example:: 79 | 80 | .. rest_parameters:: images-parameters.yaml 81 | 82 | - Content-type: Content-Type-data 83 | - image_id: image_id-in-path 84 | 85 | Where the ``images-parameters.yaml`` file contains pointers named 86 | ``Content-type`` and ``image_id`` and descriptions for each. 87 | 88 | **rest_status_code**: Enter a reference to a ``http-status.yaml`` file and 89 | indicate which errors or status codes you want to document. You can also add 90 | a pointer to more precise descriptions for each code. For example:: 91 | 92 | .. rest_status_code:: success http-codes.yaml 93 | 94 | - 204 95 | 96 | .. rest_status_code:: error http-codes.yaml 97 | 98 | - 400: informal 99 | - 401 100 | - 403 101 | - 404 102 | - 409 103 | - 410: image-data-410 104 | - 413: image-data-413 105 | - 415: image-data-415 106 | - 500: informal 107 | - 503: image-data-503 108 | 109 | 110 | * Free software: Apache license 111 | * Documentation: https://docs.openstack.org/os-api-ref/latest/ 112 | * Source: https://opendev.org/openstack/os-api-ref 113 | -------------------------------------------------------------------------------- /RELEASING.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Releasing 3 | =========== 4 | 5 | This documents the how and when to release ``os-api-ref``. 6 | 7 | When to release 8 | =============== 9 | 10 | Any time there are fixes or additions ready to go, they should be 11 | released. Releases are cheap. If it's been more than a month and there 12 | are changes in master, consider releasing them. 13 | 14 | If the changes are entirely on the CSS / JS cosmetic side, things are 15 | usually pretty safe to release as long as they have been spot checked 16 | against a couple of projects. (The gate does the nova tree 17 | automatically). 18 | 19 | If new warnings are added 20 | ------------------------- 21 | 22 | If **new** warnings have been added since the last release, care 23 | should be taken to: 24 | 25 | * Alert the mailing list 2 days before the release about the new 26 | warning coming in (that should give them time to go non enforcing 27 | or fix the issue). 28 | * Ensure that you bump at least the Y in the version number 29 | (X.Y.Z). New warnings are not a Z level release. 30 | 31 | How to release 32 | ============== 33 | 34 | Check out ``openstack/releases`` 35 | 36 | Edit ``deliverables/_independent/os-api-ref.yaml`` 37 | 38 | Add a line with the version number desired, the git has of the commit 39 | that should be that release. 40 | 41 | If you have questions ask in ``#openstack-release`` 42 | -------------------------------------------------------------------------------- /TESTS.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Tests that should be written 3 | ============================== 4 | 5 | Negative 6 | 7 | * test warning when groups are out of order 8 | * test when parameter keys are missing 9 | * test when description RST is invalid 10 | * test when parameter file is missing 11 | 12 | Microversion 13 | 14 | * test attributes are set when microversions are annotated. 15 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | sphinx>=4.0.0 # BSD 5 | openstackdocstheme>=2.2.1 # Apache-2.0 6 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Red Hat, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | import sys 18 | 19 | sys.path.insert(0, os.path.abspath('../..')) 20 | # -- General configuration ---------------------------------------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be 23 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = [ 25 | 'sphinx.ext.autodoc', 26 | 'openstackdocstheme' 27 | ] 28 | 29 | # openstackdocstheme options 30 | openstackdocs_repo_name = 'openstack/os-api-ref' 31 | openstackdocs_auto_name = False 32 | openstackdocs_bug_project = 'openstack-doc-tools' 33 | openstackdocs_bug_tag = 'os-api-ref' 34 | 35 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 36 | # text edit cycles. 37 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 38 | 39 | # The suffix of source filenames. 40 | source_suffix = '.rst' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = 'os-api-ref' 47 | copyright = '2018, The contributors' 48 | 49 | # If true, '()' will be appended to :func: etc. cross-reference text. 50 | add_function_parentheses = True 51 | 52 | # If true, the current module name will be prepended to all description 53 | # unit titles (such as .. function::). 54 | add_module_names = True 55 | 56 | # The name of the Pygments (syntax highlighting) style to use. 57 | pygments_style = 'native' 58 | 59 | # -- Options for HTML output -------------------------------------------------- 60 | 61 | # The theme to use for HTML and HTML Help pages. Major themes that come with 62 | # Sphinx are currently 'default' and 'sphinxdoc'. 63 | # html_theme_path = ["."] 64 | # html_theme = '_theme' 65 | # html_static_path = ['static'] 66 | 67 | html_theme = 'openstackdocs' 68 | 69 | # Output file base name for HTML help builder. 70 | htmlhelp_basename = '%sdoc' % project 71 | 72 | # Grouping the document tree into LaTeX files. List of tuples 73 | # (source start file, target name, title, author, documentclass 74 | # [howto/manual]). 75 | latex_documents = [ 76 | ('index', 77 | '%s.tex' % project, 78 | '%s Documentation' % project, 79 | 'OpenStack Foundation', 'manual'), 80 | ] 81 | -------------------------------------------------------------------------------- /doc/source/contributing.rst: -------------------------------------------------------------------------------- 1 | 2 | Contributing 3 | ============ 4 | .. include:: ../../CONTRIBUTING.rst 5 | -------------------------------------------------------------------------------- /doc/source/http-status.yaml: -------------------------------------------------------------------------------- 1 | 200: 2 | default: | 3 | Request was successful. 4 | image-data-200: | 5 | The service lists the image data in the response body. 6 | 201: 7 | default: | 8 | Request has been fulfilled and new resource created. 9 | 202: 10 | default: | 11 | Request is accepted, but processing may take some time. 12 | 203: 13 | default: | 14 | Returned information is not full set, but a subset. 15 | 204: 16 | default: | 17 | Request fulfilled but service does not return anything. 18 | 300: 19 | default: | 20 | The resource corresponds to more than one representation. 21 | 400: 22 | default: | 23 | Some content in the request was invalid. 24 | 401: 25 | default: | 26 | User must authenticate before making a request. 27 | 403: 28 | default: | 29 | Policy does not allow current user to do this operation. 30 | 404: 31 | default: | 32 | The requested resource could not be found. 33 | 405: 34 | default: | 35 | Method is not valid for this endpoint and resource. 36 | 409: 37 | default: | 38 | This resource has an action in progress that would conflict with this request. 39 | 413: 40 | default: | 41 | This operation cannot be completed. 42 | image-data-413: | 43 | The payload cannot be accepted. Possible causes include: 44 | * The backend storage is full. 45 | * This request added to your existing image data exceeds your total 46 | storage quota for images. 47 | * The image payload submitted with this request exceeds the maximum 48 | allowable image size. 49 | 415: 50 | default: | 51 | The entity of the request is in a format not supported by the requested 52 | resource for the method. 53 | 500: 54 | default: | 55 | Something went wrong with the service which prevents it from fulfilling 56 | the request. 57 | 501: 58 | default: | 59 | The service does not have the functionality required to fulfill this 60 | request. 61 | 503: 62 | default: | 63 | The service cannot handle the request right now. 64 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ============================================== 2 | Welcome to os-api-ref developer documentation! 3 | ============================================== 4 | 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | installation 10 | usage 11 | contributing 12 | 13 | Indices and tables 14 | ================== 15 | 16 | * :ref:`genindex` 17 | * :ref:`search` 18 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | At the command line:: 5 | 6 | $ pip install os-api-ref 7 | 8 | Or, if you have virtualenvwrapper installed:: 9 | 10 | $ mkvirtualenv os-api-ref 11 | $ pip install os-api-ref 12 | -------------------------------------------------------------------------------- /doc/source/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | ``os-api-ref`` is designed to be used inside of a sphinx tree that is 5 | devoted solely to the documentation of the API. 6 | 7 | Modify your ``source/conf.py`` file to include ``os_api_ref`` in the 8 | list of sphinx extensions. This extension assumes you are also using 9 | ``openstackdocstheme`` for some of the styling, and may not fully work if you 10 | are not. 11 | 12 | .. code-block:: python 13 | 14 | # Add any Sphinx extension module names here, as strings. They can be 15 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 16 | 17 | extensions = [ 18 | 'os_api_ref', 19 | ] 20 | 21 | 22 | Stanzas 23 | ======= 24 | 25 | rest_method 26 | ----------- 27 | 28 | The ``rest_method`` stanza is a way to declare that a section is about 29 | a particular REST method. It takes the form of: 30 | 31 | .. code-block:: rst 32 | 33 | .. rest_method:: 34 | 35 | ``METHODNAME`` should be one of the commonly used REST methods or HTTP verbs. 36 | This stanza should be the first element in a ``section`` that has some 37 | descriptive title about the method. An example from the Nova 38 | documentation is: 39 | 40 | .. code-block:: rst 41 | 42 | List Servers 43 | ============ 44 | 45 | .. rest_method:: GET /v2.1/{tenant_id}/servers 46 | 47 | Lists IDs, names, and links for all servers. 48 | 49 | 50 | Servers contain a status attribute that indicates the current server 51 | state. You can filter on the server status when you complete a list 52 | servers request. The server status is returned in the response 53 | body. The possible server status values are: 54 | 55 | ... 56 | 57 | This is going to do a slightly unexpected transform where the 58 | ``rest_method`` is pivoted up and into the section title to produce an 59 | HTML line of the form: . 61 | 62 | The entire contents of the ``List Servers`` section will then be 63 | hidden by default, with a button to open it on demand. 64 | 65 | rest_parameters 66 | --------------- 67 | 68 | The ``rest_parameters`` stanza is a solution to the problem of tables 69 | in ``rst``. 70 | 71 | A REST API that uses JSON has a large number of structured parameters 72 | that include type, location (i.e. is this in the query, the header, 73 | the path, the body), whether or not this parameter is required, as well as 74 | the desire to provide a long description about each parameter. And, assuming 75 | some consistent modeling, that parameter will show up in multiple calls. A 76 | ``server_id`` used in the path is always going to have the same 77 | meaning. 78 | 79 | It is natural to want to display this data in a tabular way to show 80 | all these dimensions. However, tables in ``rst`` are quite cumbersome, and 81 | repeating the same data over and over again is error prone. 82 | 83 | The ``rest_parameters`` stanza solves this by having the inline markup 84 | be a yaml list of ``name: value`` pairs. ``name`` is the name of the 85 | parameter. ``value`` is the key to lookup the rest of the details for 86 | this parameter in a parameters file. In each method, 87 | there should be one ``rest_parameters`` stanza for the request, and 88 | another ``rest_parameters`` stanza for the response. 89 | 90 | .. code-block:: rst 91 | 92 | .. rest_parameters:: parameters.yaml 93 | 94 | - tenant_id: tenant_id 95 | - changes-since: changes-since 96 | - image: image_query 97 | - flavor: flavor_query 98 | - name: server_name_query 99 | - status: server_status_query 100 | - host: host_query 101 | - limit: limit 102 | - marker: marker 103 | 104 | And corresponding entries in ``parameters.yaml``: 105 | 106 | .. code-block:: yaml 107 | 108 | tenant_id: 109 | description: | 110 | The UUID of the tenant in a multi-tenancy cloud. 111 | in: path 112 | required: true 113 | type: string 114 | ... 115 | changes-since: 116 | description: | 117 | Filters the response by a date and time when the image last changed status. 118 | Use this query parameter to check for changes since a previous request rather 119 | than re-downloading and re-parsing the full status at each polling interval. 120 | If data has changed, the call returns only the items changed since the ``changes-since`` 121 | time. If data has not changed since the ``changes-since`` time, the call returns an 122 | empty list.\nTo enable you to keep track of changes, this filter also displays images 123 | that were deleted if the ``changes-since`` value specifies a date in the last 30 days. 124 | Items deleted more than 30 days ago might be returned, but it is not guaranteed. 125 | The date and time stamp format is `ISO 8601 `_: 126 | 127 | :: 128 | 129 | CCYY-MM-DDThh:mm:ss±hh:mm 130 | 131 | The ``±hh:mm`` value, if included, returns the time zone as an offset from UTC. 132 | For example, ``2015-08-27T09:49:58-05:00``. 133 | If you omit the time zone, the UTC time zone is assumed. 134 | in: query 135 | required: false 136 | type: string 137 | server_status_query: 138 | description: | 139 | Filters the response by a server status, as a string. For example, ``ACTIVE``. 140 | in: query 141 | required: false 142 | type: string 143 | 144 | Every ``rest_parameters`` stanza specifies the lookup file it will 145 | use. This gives you the freedom to decide how you would like to split 146 | up your parameters, ranging from a single global file, to a dedicated 147 | file for every stanza, or anywhere in between. 148 | 149 | parameters file format 150 | ---------------------- 151 | 152 | The parameters file is inspired by the OpenAPI (aka: Swagger) 153 | specification. The OpenAPI specification provides a property object 154 | which categorizes the parameters by type and describes how the parameter is used. 155 | The following fields exist for every entry: 156 | 157 | in 158 | where this parameter exists. One of ``header``, ``path``, 159 | ``query``, ``body``. 160 | 161 | description 162 | a free form description of the parameter. This can be 163 | multiline (if using the | or > tags in yaml), and supports ``rst`` 164 | format syntax. 165 | 166 | required 167 | whether this parameter is required or not. If ``required: 168 | false`` the parameter name will be rendered with an (Optional) 169 | keyword next to it 170 | 171 | type 172 | the javascript/json type of the field. one of ``boolean``, ``int``, 173 | ``float``, ``string``, ``uuid``, ``array``, ``object``. 174 | 175 | min_version 176 | the microversion that this parameter was introduced at. Will render 177 | a *new in $version* stanza in the html output. 178 | 179 | max_version 180 | the last version that includes this parameter. Will render 181 | a *Available until $version* stanza in the html output. 182 | 183 | 184 | rest_status_code 185 | ---------------- 186 | 187 | The ``rest_status_code`` stanza is how you can show what HTTP status codes your 188 | API uses and what they indicate. 189 | 190 | .. code-block:: rst 191 | 192 | .. rest_status_code:: 193 | 194 | This stanza should be the first element after the narrative section of the 195 | method description. 196 | 197 | An example from the Designate documentation is: 198 | 199 | .. code-block:: rst 200 | :emphasize-lines: 11-25 201 | 202 | Create Zone 203 | =========== 204 | 205 | .. rest_method:: POST /v2/zones 206 | 207 | Create a zone 208 | 209 | Response codes 210 | -------------- 211 | 212 | .. rest_status_code:: success status.yaml 213 | 214 | - 200 215 | - 100 216 | - 201 217 | 218 | 219 | .. rest_status_code:: error status.yaml 220 | 221 | - 405 222 | - 403 223 | - 401 224 | - 400 225 | - 500 226 | - 409: duplicate_zone 227 | 228 | And corresponding entries in ``http-status.yaml``: 229 | 230 | .. code-block:: yaml 231 | 232 | 100: 233 | default: | 234 | An unusual code for an API 235 | 200: 236 | default: | 237 | Request was successful. 238 | 201: 239 | default: > 240 | Request has been fulfilled and new resource created. The ``Location`` header 241 | has the URL to the new item. 242 | 400: 243 | default: | 244 | Some content in the request was invalid 245 | zone_data_error: | 246 | Some of the data for the zone in the request is unavailable to the service. 247 | 401: 248 | default: | 249 | User must authenticate before making a request. 250 | 403: 251 | default: | 252 | Policy does not allow current user to do this operation. 253 | 405: 254 | default: | 255 | Method is not valid for this endpoint and resource. 256 | 409: 257 | default: | 258 | This resource has an action in progress that would conflict with this request. 259 | duplicate_zone: | 260 | There is already a zone with this name. 261 | 500: 262 | default: | 263 | Something went wrong with the service which prevents it from fulfilling the request. 264 | 265 | This RST example creates two HTML tables of response codes, one for success and one for 266 | errors. 267 | 268 | status file format 269 | ------------------ 270 | 271 | This is a simple yaml file, with a single object of status codes and the 272 | reasons that each would be used. 273 | 274 | Each status code **must** have a default entry in the status yaml file. The default entry is used 275 | in the ``rest_status_code`` stanza when a code is listed with no value or lookup key. 276 | 277 | There may be situations where the reason for a code may be different across 278 | endpoints, or a different message may be appropriate. 279 | 280 | In this case, adding a entry at the same level as the ``default`` and 281 | referencing that in the stanza like so: 282 | 283 | .. code-block:: yaml 284 | 285 | - 409: duplicate_zone 286 | 287 | This will override the default message with the newly defined one. 288 | 289 | You can get a copy of a starter status file from the os-api-ref repository, 290 | by downloading :download:`http-status.yaml `. 291 | 292 | rest_expand_all 293 | --------------- 294 | 295 | The ``rest_expand_all`` stanza is used to place a control in the 296 | document that will be a global Show / Hide for all sections. There are 297 | times when this is extremely nice to have. 298 | 299 | 300 | Including Sample Files 301 | ====================== 302 | 303 | To refer to a sample file in a ``rst`` file, use the 304 | ``rst`` directive, ``literalinclude``. Typically, the content sent 305 | or received is of type JSON, so the language role is set to javascript. 306 | The example immediately follows the parameter listing in the ``rst`` file. 307 | An example of an included Nova response sample file: 308 | 309 | .. code-block:: rst 310 | 311 | .. literalinclude:: ../../doc/api_samples/os-evacuate/server-evacuate-resp.json 312 | :language: javascript 313 | 314 | 315 | Runtime Warnings 316 | ================ 317 | 318 | The extension tries to help when it can by pointing out that something isn't 319 | matching up correctly. The following warnings are generated when 320 | issues are found: 321 | 322 | * parameters file is not found 323 | * parameters file is not valid yaml, i.e. 324 | missing colon after the name 325 | * a lookup value in the ``rst`` file is not found in the parameters file 326 | * the parameters file is not sorted as outlined in the rules below 327 | 328 | The sorting rules for parameters file is that first elements should be 329 | sorted by ``in``, going from earliest to latest processed. 330 | 331 | #. header 332 | #. path 333 | #. query 334 | #. body 335 | 336 | After that, the parameters should be sorted by name, lower case alpha 337 | numerically. 338 | 339 | The sort enforcement is because in large parameters files it helps 340 | prevent unintended duplicates. 341 | -------------------------------------------------------------------------------- /os_api_ref/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from collections import OrderedDict 14 | import hashlib 15 | import os 16 | import re 17 | 18 | from docutils import nodes 19 | from docutils.parsers import rst 20 | from docutils.parsers.rst.directives.tables import Table 21 | from docutils.statemachine import ViewList 22 | import pbr.version 23 | from sphinx.util import logging 24 | from sphinx.util.osutil import copyfile 25 | import yaml 26 | 27 | from os_api_ref.http_codes import http_code 28 | from os_api_ref.http_codes import http_code_html 29 | from os_api_ref.http_codes import http_code_text 30 | from os_api_ref.http_codes import HTTPResponseCodeDirective 31 | 32 | __version__ = pbr.version.VersionInfo( 33 | 'os_api_ref').version_string() 34 | 35 | LOG = logging.getLogger(__name__) 36 | 37 | """This provides a sphinx extension able to create the HTML needed 38 | for the api-ref website. 39 | 40 | It contains 2 new stanzas. 41 | 42 | .. rest_method:: GET /foo/bar 43 | 44 | Which is designed to be used as the first stanza in a new section to 45 | state that section is about that REST method. During processing the 46 | rest stanza will be reparented to be before the section in question, 47 | and used as a show/hide selector for it's details. 48 | 49 | .. rest_parameters:: file.yaml 50 | 51 | - name1: name_in_file1 52 | - name2: name_in_file2 53 | - name3: name_in_file3 54 | 55 | Which is designed to build structured tables for either response or 56 | request parameters. The stanza takes a value which is a file to lookup 57 | details about the parameters in question. 58 | 59 | The contents of the stanza are a yaml list of key / value pairs. The 60 | key is the name of the parameter to be shown in the table. The value 61 | is the key in the file.yaml where all other metadata about the 62 | parameter will be extracted. This allows for reusing parameter 63 | definitions widely in API definitions, but still providing for control 64 | in both naming and ordering of parameters at every declaration. 65 | 66 | """ 67 | 68 | 69 | def ordered_load( 70 | stream, Loader=yaml.SafeLoader, object_pairs_hook=OrderedDict): 71 | """Load yaml as an ordered dict 72 | 73 | This allows us to inspect the order of the file on disk to make 74 | sure it was correct by our rules. 75 | """ 76 | class OrderedLoader(Loader): 77 | pass 78 | 79 | def construct_mapping(loader, node): 80 | loader.flatten_mapping(node) 81 | return object_pairs_hook(loader.construct_pairs(node)) 82 | 83 | OrderedLoader.add_constructor( 84 | yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, 85 | construct_mapping) 86 | # for parameters.yaml we treat numbers (especially version 87 | # numbers) as strings. So that microversion specification of 2.20 88 | # and 2.2 don't get confused. 89 | OrderedLoader.add_constructor( 90 | 'tag:yaml.org,2002:float', 91 | yaml.constructor.SafeConstructor.construct_yaml_str) 92 | 93 | return yaml.load(stream, OrderedLoader) 94 | 95 | 96 | class rest_method(nodes.Part, nodes.Element): 97 | """Node for rest_method stanza 98 | 99 | Because we need to insert very specific HTML at the final stage of 100 | processing, the rest_method stanza needs a custom node type. This 101 | lets us accumulate the relevant data into this node, during 102 | parsing, but not turn it into known sphinx types (lists, tables, 103 | sections). 104 | 105 | Then, during the final build phase we transform directly to the 106 | html that we want. 107 | 108 | NOTE: this means we error trying to build latex or man pages for 109 | these stanza types right now. This is all fixable if we add an 110 | output formatter for this node type, but it's not yet a 111 | priority. Contributions welcomed. 112 | """ 113 | pass 114 | 115 | 116 | class rest_expand_all(nodes.Part, nodes.Element): 117 | """A node placeholder for the expand all button. 118 | 119 | This is a node that we can insert into the doctree which on final 120 | render can be converted to the custom HTML we need for the expand 121 | all button. It is automatically inserted at the top of the page 122 | for API ref documents. 123 | """ 124 | pass 125 | 126 | 127 | class RestExpandAllDirective(rst.Directive): 128 | # This tells sphinx that the directive will need to generate 129 | # content during the final build phase. 130 | has_content = True 131 | 132 | def run(self): 133 | app = self.state.document.settings.env.app 134 | node = rest_expand_all() 135 | max_ver = app.config.os_api_ref_max_microversion 136 | min_ver = app.config.os_api_ref_min_microversion 137 | releases = app.config.os_api_ref_release_microversions 138 | node['major'] = None 139 | try: 140 | if max_ver.split('.')[0] == min_ver.split('.')[0]: 141 | node['max_ver'] = int(max_ver.split('.')[1]) 142 | node['min_ver'] = int(min_ver.split('.')[1]) 143 | node['major'] = int(max_ver.split('.')[0]) 144 | node['releases'] = releases 145 | except ValueError: 146 | # TODO(sdague): warn that we're ignoring this all 147 | pass 148 | except IndexError: 149 | pass 150 | return [node] 151 | 152 | 153 | class RestMethodDirective(rst.Directive): 154 | 155 | # this enables content in the directive 156 | has_content = True 157 | 158 | @staticmethod 159 | def find_param(content, name): 160 | for line in content: 161 | if ("%s: " % name) in line: 162 | _, value = line.split(': ') 163 | return value.rstrip().lstrip() 164 | return None 165 | 166 | def run(self): 167 | lineno = self.state_machine.abs_line_number() 168 | target = nodes.target() 169 | section = nodes.section(classes=["detail-control"]) 170 | 171 | node = rest_method() 172 | 173 | # TODO(sdague): this is a super simplistic parser, should be 174 | # more robust. 175 | method, sep, url = self.content[0].partition(' ') 176 | node['min_version'] = self.find_param(self.content, 'min_version') 177 | node['max_version'] = self.find_param(self.content, 'max_version') 178 | 179 | node['method'] = method 180 | node['url'] = url 181 | 182 | # Extract the path parameters from the url 183 | env = self.state.document.settings.env 184 | env.path_params = [] 185 | env.path_params = re.findall("{[a-zA-Z][a-zA-Z_0-9]*}", url) 186 | 187 | node['target'] = self.state.parent.attributes['ids'][0] 188 | node['css_classes'] = "" 189 | if node['min_version']: 190 | node['css_classes'] += "rp_min_ver_%s " % ( 191 | str(node['min_version']).replace('.', '_')) 192 | if node['max_version']: 193 | node['css_classes'] += "rp_max_ver_%s " % ( 194 | str(node['max_version']).replace('.', '_')) 195 | 196 | # We need to build a temporary target that we can replace 197 | # later in the processing to get the TOC to resolve correctly. 198 | # SHA-1 is used even if collisions are possible, because 199 | # they are still unlikely to occurr and it is way shorter 200 | # than stronger SHAs. 201 | node_hash = hashlib.sha1(str(node).encode('utf-8')).hexdigest() 202 | temp_target = "{}-{}-selector".format(node['target'], node_hash) 203 | target = nodes.target(ids=[temp_target]) 204 | self.state.add_target(temp_target, '', target, lineno) 205 | section += node 206 | 207 | return [target, section] 208 | 209 | 210 | # cache for file -> yaml so we only do the load and check of a yaml 211 | # file once during a sphinx processing run. 212 | YAML_CACHE = {} 213 | 214 | 215 | class RestParametersDirective(Table): 216 | 217 | headers = ["Name", "In", "Type", "Description"] 218 | 219 | def _load_param_file(self, fpath): 220 | global YAML_CACHE 221 | if fpath in YAML_CACHE: 222 | return YAML_CACHE[fpath] 223 | 224 | lookup = {} 225 | try: 226 | with open(fpath) as stream: 227 | lookup = ordered_load(stream) 228 | except OSError: 229 | LOG.warning("Parameters file not found, %s", fpath, 230 | location=(self.env.docname, None)) 231 | return 232 | except yaml.YAMLError as exc: 233 | LOG.exception(exc_info=exc, 234 | msg="Error while parsing file [%s]." % fpath) 235 | raise 236 | 237 | if lookup: 238 | self._check_yaml_sorting(fpath, lookup) 239 | else: 240 | LOG.warning("Parameters file is empty, %s", fpath, 241 | location=(self.env.docname, None)) 242 | return 243 | 244 | YAML_CACHE[fpath] = lookup 245 | return lookup 246 | 247 | def _check_yaml_sorting(self, fpath, yaml_data): 248 | """check yaml sorting 249 | 250 | Assuming we got an ordered dict, we iterate through it 251 | basically doing a gnome sort test 252 | (https://en.wikipedia.org/wiki/Gnome_sort) and ensure the item 253 | we are looking at is > the last item we saw. This is done at 254 | the section level first, so we're grouped, then alphabetically 255 | by lower case name within a section. Every time there is a 256 | mismatch we raise an warn message. 257 | """ 258 | sections = {"header": 1, "path": 2, "query": 3, "body": 4} 259 | 260 | last = None 261 | for key, value in yaml_data.items(): 262 | if not isinstance(value, dict): 263 | raise Exception('Expected a dict for {0}; got {0}={1}).\n' 264 | 'You probably have indentation typo in your' 265 | 'YAML source'.format(key, value)) 266 | 267 | # use of an invalid 'in' value 268 | if value['in'] not in sections: 269 | LOG.warning("``%s`` is not a valid value for 'in' (must be " 270 | "one of: %s). (see ``%s``)", 271 | value['in'], 272 | ", ".join(sorted(sections.keys())), 273 | key) 274 | continue 275 | 276 | if last is None: 277 | last = (key, value) 278 | continue 279 | # ensure that sections only go up 280 | current_section = value['in'] 281 | last_section = last[1]['in'] 282 | if sections[current_section] < sections[last_section]: 283 | LOG.warning("Section out of order. All parameters in section " 284 | "``%s`` should be after section ``%s``. (see " 285 | "``%s``)", last_section, current_section, last[0]) 286 | if (sections[value['in']] == sections[last[1]['in']] and 287 | key.lower() < last[0].lower()): 288 | LOG.warning("Parameters out of order ``%s`` should be after " 289 | "``%s``", last[0], key) 290 | last = (key, value) 291 | 292 | def yaml_from_file(self, fpath): 293 | """Collect Parameter stanzas from inline + file. 294 | 295 | This allows use to reference an external file for the actual 296 | parameter definitions. 297 | """ 298 | 299 | lookup = self._load_param_file(fpath) 300 | if not lookup: 301 | return 302 | 303 | content = "\n".join(self.content) 304 | parsed = yaml.safe_load(content) 305 | new_content = list() 306 | for paramlist in parsed: 307 | if not isinstance(paramlist, dict): 308 | location = (self.state_machine.node.source, 309 | self.state_machine.node.line) 310 | LOG.warning("Invalid parameter definition ``%s``. Expected " 311 | "format: ``name: reference``. Skipping.", 312 | paramlist, location=location) 313 | continue 314 | for name, ref in paramlist.items(): 315 | if ref in lookup: 316 | new_content.append((name, lookup[ref])) 317 | else: 318 | # TODO(sdague): this provides a kind of confusing 319 | # error message because app.warn isn't meant to be 320 | # used this way, however it does provide a way to 321 | # track down where the parameters list is that is 322 | # wrong. So it's good enough for now. 323 | location = (self.state_machine.node.source, 324 | self.state_machine.node.line) 325 | LOG.warning("No field definition for ``%s`` found in " 326 | "``%s``. Skipping.", ref, fpath) 327 | 328 | # Check for path params in stanza 329 | for i, param in enumerate(self.env.path_params): 330 | if (param.rstrip('}').lstrip('{')) == name: 331 | del self.env.path_params[i] 332 | break 333 | else: 334 | continue 335 | 336 | if len(self.env.path_params) != 0: 337 | # Warn that path parameters are not set in rest_parameter 338 | # stanza and will not appear in the generated table. 339 | for param in self.env.path_params: 340 | location = (self.state_machine.node.source, 341 | self.state_machine.node.line) 342 | LOG.warning("No path parameter ``%s`` found in rest_parameter" 343 | " stanza.\n", param.rstrip('}').lstrip('{')) 344 | 345 | self.yaml = new_content 346 | 347 | def run(self): 348 | self.env = self.state.document.settings.env 349 | 350 | # Make sure we have some content, which should be yaml that 351 | # defines some parameters. 352 | if not self.content: 353 | error = self.state_machine.reporter.error( 354 | 'No parameters defined', 355 | nodes.literal_block(self.block_text, self.block_text), 356 | line=self.lineno) 357 | return [error] 358 | 359 | if not len(self.arguments) >= 1: 360 | error = self.state_machine.reporter.error( 361 | 'No reference file defined', 362 | nodes.literal_block(self.block_text, self.block_text), 363 | line=self.lineno) 364 | return [error] 365 | 366 | # NOTE(sdague): it's important that we pop the arg otherwise 367 | # we end up putting the filename as the table caption. 368 | rel_fpath, fpath = self.env.relfn2path(self.arguments.pop()) 369 | self.yaml_file = fpath 370 | self.yaml_from_file(self.yaml_file) 371 | 372 | self.max_cols = len(self.headers) 373 | # TODO(sdague): it would be good to dynamically set column 374 | # widths (or basically make the colwidth thing go away 375 | # entirely) 376 | self.options['widths'] = [20, 10, 10, 60] 377 | self.col_widths = self.get_column_widths(self.max_cols) 378 | if isinstance(self.col_widths, tuple): 379 | # In docutils 0.13.1, get_column_widths returns a (widths, 380 | # colwidths) tuple, where widths is a string (i.e. 'auto'). 381 | # See https://sourceforge.net/p/docutils/patches/120/. 382 | self.col_widths = self.col_widths[1] 383 | # Actually convert the yaml 384 | title, messages = self.make_title() 385 | table_node = self.build_table() 386 | self.add_name(table_node) 387 | if title: 388 | table_node.insert(0, title) 389 | return [table_node] + messages 390 | 391 | def get_rows(self, table_data): 392 | rows = [] 393 | groups = [] 394 | trow = nodes.row() 395 | entry = nodes.entry() 396 | para = nodes.paragraph(text=str(table_data)) 397 | entry += para 398 | trow += entry 399 | rows.append(trow) 400 | return rows, groups 401 | 402 | # Add a column for a field. In order to have the RST inside 403 | # these fields get rendered, we need to use the 404 | # ViewList. Note, ViewList expects a list of lines, so chunk 405 | # up our content as a list to make it happy. 406 | def add_col(self, value): 407 | entry = nodes.entry() 408 | result = ViewList(value.split('\n')) 409 | self.state.nested_parse(result, 0, entry) 410 | return entry 411 | 412 | def show_no_yaml_error(self): 413 | trow = nodes.row(classes=["no_yaml"]) 414 | trow += self.add_col("No yaml found %s" % self.yaml_file) 415 | trow += self.add_col("") 416 | trow += self.add_col("") 417 | trow += self.add_col("") 418 | return trow 419 | 420 | def collect_rows(self): 421 | rows = [] 422 | groups = [] 423 | try: 424 | for key, values in self.yaml: 425 | min_version = values.get('min_version', '') 426 | max_version = values.get('max_version', '') 427 | desc = values.get('description', '') 428 | classes = [] 429 | if min_version: 430 | desc += ("\n\n**New in version %s**\n" % min_version) 431 | min_ver_css_name = ("rp_min_ver_" + 432 | str(min_version).replace('.', '_')) 433 | classes.append(min_ver_css_name) 434 | if max_version: 435 | desc += ("\n\n**Available until version %s**\n" % 436 | max_version) 437 | max_ver_css_name = ("rp_max_ver_" + 438 | str(max_version).replace('.', '_')) 439 | classes.append(max_ver_css_name) 440 | trow = nodes.row(classes=classes) 441 | name = key 442 | if values.get('required', False) is False: 443 | name += " (Optional)" 444 | trow += self.add_col(name) 445 | trow += self.add_col(values.get('in')) 446 | trow += self.add_col(values.get('type')) 447 | trow += self.add_col(desc) 448 | rows.append(trow) 449 | except AttributeError as exc: 450 | if 'key' in locals(): 451 | LOG.warning("Failure on key: %s, values: %s. %s", 452 | key, values, exc) 453 | else: 454 | rows.append(self.show_no_yaml_error()) 455 | return rows, groups 456 | 457 | def build_table(self): 458 | table = nodes.table() 459 | tgroup = nodes.tgroup(cols=len(self.headers)) 460 | table += tgroup 461 | 462 | # TODO(sdague): it would be really nice to figure out how not 463 | # to have this stanza, it kind of messes up all of the table 464 | # formatting because it doesn't let tables just be the right 465 | # size. 466 | tgroup.extend( 467 | nodes.colspec(colwidth=col_width, colname='c' + str(idx)) 468 | for idx, col_width in enumerate(self.col_widths) 469 | ) 470 | 471 | thead = nodes.thead() 472 | tgroup += thead 473 | 474 | row_node = nodes.row() 475 | thead += row_node 476 | row_node.extend(nodes.entry(h, nodes.paragraph(text=h)) 477 | for h in self.headers) 478 | 479 | tbody = nodes.tbody() 480 | tgroup += tbody 481 | 482 | rows, groups = self.collect_rows() 483 | tbody.extend(rows) 484 | table.extend(groups) 485 | 486 | return table 487 | 488 | 489 | def rest_method_html(self, node): 490 | tmpl = """ 491 |
492 |
493 |
494 |
495 | 498 | 499 | %(method)s 500 |
501 |
502 |
503 |
504 |
505 |
%(url)s
506 |
507 |

%(desc)s

508 |
509 |
510 |
511 |
512 |
513 | 519 |
520 |
521 |
""" 522 | 523 | node['url'] = node['url'].replace( 524 | '{', 525 | '{') 526 | node['url'] = node['url'].replace( 527 | '}', 528 | '}') 529 | 530 | self.body.append(tmpl % node) 531 | raise nodes.SkipNode 532 | 533 | 534 | def rest_expand_all_html(self, node): 535 | tmpl = """ 536 |
537 | %(extra_js)s 538 |
539 | %(selector)s 540 |
541 |
542 | 546 |
547 |
""" 548 | 549 | node.setdefault('selector', "") 550 | node.setdefault('extra_js', "") 551 | 552 | if node['major']: 553 | node['selector'], node['extra_js'] = create_mv_selector(node) 554 | 555 | self.body.append(tmpl % node) 556 | raise nodes.SkipNode 557 | 558 | 559 | def rest_method_text(self, node): 560 | raise nodes.SkipNode 561 | 562 | 563 | def rest_expand_all_text(self, node): 564 | raise nodes.SkipNode 565 | 566 | 567 | def create_mv_selector(node): 568 | 569 | mv_list = '' 570 | 571 | for x in range(node['min_ver'], node['max_ver'] + 1): 572 | mv_list += build_mv_item(node['major'], x, node['releases']) 573 | 574 | selector_tmpl = """ 575 |
576 |
577 | 580 | 583 |
584 |
585 | """ 586 | 587 | js_tmpl = """ 588 | 592 | """ 593 | 594 | selector_content = { 595 | 'mv_list': mv_list 596 | } 597 | 598 | js_content = { 599 | 'min': node['min_ver'], 600 | 'max': node['max_ver'] 601 | } 602 | 603 | return selector_tmpl % selector_content, js_tmpl % js_content 604 | 605 | 606 | def build_mv_item(major, micro, releases): 607 | version = "%d.%d" % (major, micro) 608 | if version in releases: 609 | return ''.format( 610 | version, version, releases[version].capitalize()) 611 | else: 612 | return ''.format(version, version) 613 | 614 | 615 | def resolve_rest_references(app, doctree): 616 | for node in doctree.traverse(): 617 | if isinstance(node, rest_method): 618 | rest_node = node 619 | rest_method_section = node.parent 620 | rest_section = rest_method_section.parent 621 | gp = rest_section.parent 622 | 623 | # Added required classes to the top section 624 | rest_section.attributes['classes'].append('api-detail') 625 | rest_section.attributes['classes'].append('collapse') 626 | 627 | # Pop the title off the collapsed section 628 | title = rest_section.children.pop(0) 629 | rest_node['desc'] = title.children[0] 630 | 631 | # In order to get the links in the sidebar to be right, we 632 | # have to do some id flipping here late in the game. The 633 | # rest_method_section has basically had a dummy id up 634 | # until this point just to keep it from colliding with 635 | # it's parent. 636 | rest_section.attributes['ids'][0] = ( 637 | "%s-detail" % rest_section.attributes['ids'][0]) 638 | rest_method_section.attributes['ids'][0] = rest_node['target'] 639 | 640 | # Pop the overall section into it's grand parent, 641 | # right before where the current parent lives 642 | idx = gp.children.index(rest_section) 643 | rest_section.remove(rest_method_section) 644 | gp.insert(idx, rest_method_section) 645 | 646 | 647 | def copy_assets(app, exception): 648 | assets = ('api-site.css', 'api-site.js') 649 | fonts = ( 650 | 'glyphicons-halflings-regular.ttf', 651 | 'glyphicons-halflings-regular.woff' 652 | ) 653 | builders = ('html', 'readthedocs', 'readthedocssinglehtmllocalmedia') 654 | if app.builder.name not in builders or exception: 655 | return 656 | dirtree = os.path.join(app.builder.outdir, '_static/fonts') 657 | if not os.path.exists(dirtree): 658 | os.makedirs(dirtree) 659 | LOG.info('Copying assets: %s', ', '.join(assets)) 660 | LOG.info('Copying fonts: %s', ', '.join(fonts)) 661 | for asset in assets: 662 | dest = os.path.join(app.builder.outdir, '_static', asset) 663 | source = os.path.abspath(os.path.dirname(__file__)) 664 | copyfile(os.path.join(source, 'assets', asset), dest) 665 | for font in fonts: 666 | dest = os.path.join(app.builder.outdir, '_static/fonts', font) 667 | source = os.path.abspath(os.path.dirname(__file__)) 668 | copyfile(os.path.join(source, 'assets', font), dest) 669 | 670 | 671 | def add_assets(app): 672 | app.add_css_file('api-site.css') 673 | app.add_js_file('api-site.js') 674 | 675 | 676 | def setup(app): 677 | # Add some config options around microversions 678 | app.add_config_value('os_api_ref_max_microversion', '', 'env') 679 | app.add_config_value('os_api_ref_min_microversion', '', 'env') 680 | app.add_config_value('os_api_ref_release_microversions', '', 'env') 681 | # TODO(sdague): if someone wants to support latex/pdf, or man page 682 | # generation using these stanzas, here is where you'd need to 683 | # specify content specific renderers. 684 | app.add_node(rest_method, html=(rest_method_html, None), 685 | text=(rest_method_text, None)) 686 | app.add_node(rest_expand_all, html=(rest_expand_all_html, None), 687 | text=(rest_expand_all_text, None)) 688 | app.add_node(http_code, html=(http_code_html, None), 689 | text=(http_code_text, None)) 690 | 691 | # This specifies all our directives that we're adding 692 | app.add_directive('rest_parameters', RestParametersDirective) 693 | app.add_directive('rest_method', RestMethodDirective) 694 | app.add_directive('rest_expand_all', RestExpandAllDirective) 695 | app.add_directive('rest_status_code', HTTPResponseCodeDirective) 696 | 697 | # The doctree-read hook is used do the slightly crazy doc 698 | # transformation that we do to get the rest_method document 699 | # structure. 700 | app.connect('doctree-read', resolve_rest_references) 701 | 702 | # Add all the static assets to our build during the early stage of building 703 | app.connect('builder-inited', add_assets) 704 | 705 | # This copies all the assets (css, js, fonts) over to the build 706 | # _static directory during final build. 707 | app.connect('build-finished', copy_assets) 708 | 709 | return { 710 | 'parallel_read_safe': True, 711 | 'parallel_write_safe': True, 712 | 'version': __version__, 713 | } 714 | -------------------------------------------------------------------------------- /os_api_ref/assets/api-site.css: -------------------------------------------------------------------------------- 1 | tt.literal { 2 | padding: 2px 4px; 3 | font-size: 90%; 4 | color: #c7254e; 5 | white-space: nowrap; 6 | background-color: #f9f2f4; 7 | border-radius: 4px; 8 | } 9 | 10 | /* bootstrap users blockquote for pull quotes, so they are much 11 | larger, we need them smaller */ 12 | blockquote { font-size: 1em; } 13 | 14 | .docs-book-wrapper { 15 | max-width: 90% !important 16 | } 17 | 18 | tbody>tr:nth-child(odd)>td, 19 | tbody>tr:nth-child(odd)>th { 20 | background-color: #f9f9f9; 21 | } 22 | 23 | 24 | td>p { 25 | margin: 0 0 0.5em; 26 | } 27 | 28 | .operation-grp { 29 | padding-top: 0.5em; 30 | padding-bottom: 1em; 31 | } 32 | 33 | /* Ensure the method buttons and their links don't split lines when 34 | the page is narrower */ 35 | .operation { 36 | /* this moves the link icon into the gutter */ 37 | margin-left: -1.25em; 38 | margin-right: 1.25em; 39 | white-space: nowrap; 40 | } 41 | 42 | /* These make the links only show up on hover */ 43 | a.operation-anchor { 44 | visibility: hidden; 45 | } 46 | 47 | .operation-grp:hover a.operation-anchor { 48 | visibility: visible; 49 | } 50 | 51 | /* All parameter tables should be full width */ 52 | 53 | .api-detail table.docutils { 54 | width: 100%; 55 | } 56 | 57 | .versionmodified { 58 | font-weight: bold; 59 | } 60 | 61 | span.badge { 62 | /* backwards compatibility to BS3 */ 63 | border-radius: 0.25em; 64 | } 65 | .label-POST { 66 | background-color: #5cb85c; 67 | } 68 | .label-POST[href]:hover, 69 | .label-POST[href]:focus { 70 | background-color: #449d44; 71 | } 72 | .label-GET, 73 | .label-HEAD { 74 | background-color: #5bc0de; 75 | } 76 | .label-GET[href]:hover, 77 | .label-GET[href]:focus, 78 | .label-HEAD[href]:hover, 79 | .label-HEAD[href]:focus { 80 | background-color: #31b0d5; 81 | } 82 | .label-PUT, 83 | .label-PATCH { 84 | background-color: #f0ad4e; 85 | } 86 | .label-PUT[href]:hover, 87 | .label-PUT[href]:focus, 88 | .label-PATCH[href]:hover, 89 | .label-PATCH[href]:focus { 90 | background-color: #ec971f; 91 | } 92 | .label-COPY { 93 | background-color: #6666ff; 94 | } 95 | .label-COPY[href]:hover, 96 | .label-COPY[href]:focus { 97 | background-color: #6699ff; 98 | } 99 | .label-DELETE { 100 | background-color: #d9534f; 101 | } 102 | .label-DELETE[href]:hover, 103 | .label-DELETE[href]:focus { 104 | background-color: #c9302c; 105 | } 106 | 107 | button.btn-info { 108 | color: white; 109 | } 110 | 111 | .btn-detail:hover, .btn-detail:focus, 112 | .btn-expand-all:hover, .btn-expand-all:focus { 113 | color: #fff; 114 | background-color: #3b6c91; 115 | border-color: #269abc; 116 | } 117 | 118 | .btn-detail, 119 | .btn-expand-all { 120 | color: #fff; 121 | background-color: #2A4E68; 122 | } 123 | 124 | span.path_parameter { 125 | font-family: monospace; 126 | padding: 2px 4px; 127 | font-size: 90%; 128 | color: #c7254e; 129 | white-space: nowrap; 130 | background-color: #f9f2f4; 131 | border-radius: 4px; 132 | } 133 | 134 | /* for microversion selector */ 135 | .mv_selector { 136 | font-size: 0.8em; 137 | padding: 0.3em; 138 | } 139 | 140 | .mv_selector.active { 141 | color: #fff; 142 | background-color: #31b0d5; 143 | border-color: #269abc; 144 | } 145 | 146 | p.url-subtitle { 147 | color: #555; 148 | font-weight: bold; 149 | } 150 | 151 | .docs-body .section h1 { 152 | display: block; 153 | } 154 | 155 | div.docs-sidebar-toc > div > ul > li { 156 | list-style-type: none; 157 | font-size: 0.8em; 158 | font-weight: bold; 159 | } 160 | 161 | div.docs-sidebar-toc > div > ul { 162 | padding-left: 20px 163 | } 164 | 165 | div.docs-sidebar-toc > div > ul > li > ul > li { 166 | list-style-type: disc; 167 | font-weight: normal; 168 | } 169 | 170 | div.docs-top-contents { 171 | display: none; 172 | } 173 | 174 | div.endpoint-container{ 175 | padding-left: 15px; 176 | } 177 | 178 | #expand-all { 179 | margin-top: 23px; 180 | } 181 | 182 | ### Combobox Experiment 183 | @media (min-width: 768px) { 184 | .form-search .combobox-container, 185 | .form-inline .combobox-container { 186 | display: inline-block; 187 | margin-bottom: 0; 188 | vertical-align: top; 189 | } 190 | .form-search .combobox-container .input-group-addon, 191 | .form-inline .combobox-container .input-group-addon { 192 | width: auto; 193 | } 194 | } 195 | .combobox-selected .caret { 196 | display: none; 197 | } 198 | /* :not doesn't work in IE8 */ 199 | .combobox-container:not(.combobox-selected) .glyphicon-remove { 200 | display: none; 201 | } 202 | .typeahead-long { 203 | max-height: 300px; 204 | overflow-y: auto; 205 | } 206 | .control-group.error .combobox-container .add-on { 207 | color: #B94A48; 208 | border-color: #B94A48; 209 | } 210 | .control-group.error .combobox-container .caret { 211 | border-top-color: #B94A48; 212 | } 213 | .control-group.warning .combobox-container .add-on { 214 | color: #C09853; 215 | border-color: #C09853; 216 | } 217 | .control-group.warning .combobox-container .caret { 218 | border-top-color: #C09853; 219 | } 220 | .control-group.success .combobox-container .add-on { 221 | color: #468847; 222 | border-color: #468847; 223 | } 224 | .control-group.success .combobox-container .caret { 225 | border-top-color: #468847; 226 | } 227 | -------------------------------------------------------------------------------- /os_api_ref/assets/api-site.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // the list of expanded element ids 3 | var expanded = []; 4 | // whether we should sync expand changes with the location 5 | // url. We need to make this false during large scale 6 | // operations because we're using the history API, which is 7 | // expensive. So a bulk expand turns this off, expands 8 | // everything, turns it back on, then does a history sync. 9 | var should_sync = true; 10 | 11 | $(document).ready(function() { 12 | // Change the text on the expando buttons when 13 | // appropriate. This also add or removes them to the list of 14 | // expanded sections, and then syncs that list to the history 15 | // after such a change. 16 | $('.api-detail') 17 | .on('hide.bs.collapse', function(e) { 18 | processButton(this, 'detail'); 19 | var index = expanded.indexOf(this.id); 20 | if (index > -1) { 21 | expanded.splice(index, 1); 22 | } 23 | sync_expanded(); 24 | }) 25 | .on('show.bs.collapse', function(e) { 26 | processButton(this, 'close'); 27 | expanded.push(this.id); 28 | sync_expanded(); 29 | }); 30 | 31 | // Expand the world. Wires up the expand all button, it turns 32 | // off the sync while it is running to save the costs with the 33 | // history API. 34 | var expandAllActive = true; 35 | $('#expand-all').click(function () { 36 | should_sync = false; 37 | if (expandAllActive) { 38 | expandAllActive = false; 39 | $('.api-detail').collapse('show'); 40 | $('#expand-all').attr('data-toggle', ''); 41 | $(this).text('Hide All'); 42 | } else { 43 | expandAllActive = true; 44 | $('.api-detail').collapse('hide'); 45 | $('#expand-all').attr('data-toggle', 'collapse'); 46 | $(this).text('Show All'); 47 | } 48 | should_sync = true; 49 | sync_expanded(); 50 | }); 51 | 52 | // if there is an expanded parameter passed in a url, we run 53 | // through and expand all the appropriate things. 54 | if (window.location.search.substring(1).indexOf("expanded") > -1) { 55 | should_sync = false; 56 | var parts = window.location.search.substring(1).split('&'); 57 | for (var i = 0; i < parts.length; i++) { 58 | var keyval = parts[i].split('='); 59 | if (keyval[0] == "expanded" && keyval[1]) { 60 | var expanded_ids = keyval[1].split(','); 61 | for (var j = 0; j < expanded_ids.length; j++) { 62 | $('#' + expanded_ids[j]).collapse('show'); 63 | } 64 | } 65 | } 66 | should_sync = true; 67 | // This is needed because the hash *might* be inside a 68 | // collapsed section. 69 | // 70 | // NOTE(sdague): this doesn't quite seem to work while 71 | // we're changing the rest of the document. 72 | $(document.body).scrollTop($(window.location.hash).offset().top); 73 | } 74 | 75 | // Wire up microversion selector 76 | $('.mv_selector').on('click', function(e) { 77 | var version = e.currentTarget.innerHTML; 78 | // flip what is active 79 | $(this).addClass('active').siblings().removeClass('active'); 80 | if (version == "All") { 81 | reset_microversion(); 82 | } else { 83 | set_microversion(version); 84 | } 85 | }); 86 | }); 87 | /** 88 | * Helper function for setting the text, styles for expandos 89 | */ 90 | function processButton(button, text) { 91 | $('#' + $(button).attr('id') + '-btn').text(text) 92 | .toggleClass('btn-info') 93 | .toggleClass('btn-default'); 94 | } 95 | 96 | // Take the expanded array and push it into history. Because 97 | // sphinx is building css appropriate ids, they should not have 98 | // any special characters we need to encode. So we can simply join 99 | // them into a comma separated list. 100 | function sync_expanded() { 101 | if (should_sync) { 102 | var url = UpdateQueryString('expanded', expanded.join(',')); 103 | history.pushState('', 'new expand', url); 104 | } 105 | } 106 | 107 | 108 | // Generically update the query string for a url. Credit to 109 | // http://stackoverflow.com/questions/5999118/add-or-update-query-string-parameter 110 | // for making this properly generic. 111 | function UpdateQueryString(key, value, url) { 112 | if (!url) url = window.location.href; 113 | var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"), 114 | hash; 115 | 116 | if (re.test(url)) { 117 | if (typeof value !== 'undefined' && value !== null) 118 | return url.replace(re, '$1' + key + "=" + value + '$2$3'); 119 | else { 120 | hash = url.split('#'); 121 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 122 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 123 | url += '#' + hash[1]; 124 | return url; 125 | } 126 | } 127 | else { 128 | if (typeof value !== 'undefined' && value !== null) { 129 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 130 | hash = url.split('#'); 131 | url = hash[0] + separator + key + '=' + value; 132 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 133 | url += '#' + hash[1]; 134 | return url; 135 | } 136 | else 137 | return url; 138 | } 139 | } 140 | 141 | // Set the Y value of the microversion to turn on / off visibility 142 | // of components. 143 | function set_microversion(number) { 144 | var major = number.split(".")[0]; 145 | var micro = number.split(".")[1]; 146 | for (var i = os_min_mv; i <= os_max_mv; i++) { 147 | var max_class = ".rp_max_ver_" + major + "_" + i; 148 | var min_class = ".rp_min_ver_" + major + "_" + i; 149 | if (i < micro) { 150 | $(max_class).hide(400); 151 | $(min_class).show(400); 152 | } else if (i >= micro) { 153 | $(min_class).hide(400); 154 | $(max_class).show(400); 155 | } 156 | } 157 | } 158 | 159 | function reset_microversion() { 160 | $('[class^=rp_min_ver]').show(400); 161 | $('[class^=rp_max_ver]').show(400); 162 | } 163 | 164 | }; 165 | -------------------------------------------------------------------------------- /os_api_ref/assets/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/os-api-ref/6084245033f4910e7bc8437cac0d76c2087c88f9/os_api_ref/assets/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /os_api_ref/assets/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/os-api-ref/6084245033f4910e7bc8437cac0d76c2087c88f9/os_api_ref/assets/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /os_api_ref/http_codes.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | from http.client import responses 14 | 15 | from docutils import nodes 16 | from docutils.parsers.rst.directives.tables import Table 17 | from docutils.statemachine import ViewList 18 | from sphinx.util import logging 19 | import yaml 20 | 21 | LOG = logging.getLogger(__name__) 22 | 23 | # cache for file -> yaml so we only do the load and check of a yaml 24 | # file once during a sphinx processing run. 25 | HTTP_YAML_CACHE = {} 26 | 27 | 28 | class HTTPResponseCodeDirective(Table): 29 | 30 | headers = ["Code", "Reason"] 31 | 32 | status_types = ("success", "error") 33 | 34 | # This is for HTTP response codes that OpenStack may use that are not part 35 | # the httplib response dict. 36 | CODES = { 37 | 429: "Too Many Requests", 38 | } 39 | 40 | required_arguments = 2 41 | 42 | def __init__(self, *args, **kwargs): 43 | self.CODES.update(responses) 44 | super().__init__(*args, **kwargs) 45 | 46 | def _load_status_file(self, fpath): 47 | global HTTP_YAML_CACHE 48 | if fpath in HTTP_YAML_CACHE: 49 | return HTTP_YAML_CACHE[fpath] 50 | 51 | # LOG.info("Fpath: %s" % fpath) 52 | try: 53 | with open(fpath) as stream: 54 | lookup = yaml.safe_load(stream) 55 | except OSError: 56 | LOG.warning( 57 | "Parameters file %s not found" % fpath, 58 | (self.env.docname, None)) 59 | return 60 | except yaml.YAMLError as exc: 61 | LOG.warning(exc) 62 | raise 63 | 64 | HTTP_YAML_CACHE[fpath] = lookup 65 | return lookup 66 | 67 | def run(self): 68 | self.env = self.state.document.settings.env 69 | 70 | # Make sure we have some content, which should be yaml that 71 | # defines some parameters. 72 | if not self.content: 73 | error = self.state_machine.reporter.error( 74 | 'No parameters defined', 75 | nodes.literal_block(self.block_text, self.block_text), 76 | line=self.lineno) 77 | return [error] 78 | 79 | if not len(self.arguments) >= 2: 80 | error = self.state_machine.reporter.error( 81 | '%s' % self.arguments, 82 | nodes.literal_block(self.block_text, self.block_text), 83 | line=self.lineno) 84 | return [error] 85 | 86 | _, status_defs_file = self.env.relfn2path(self.arguments.pop()) 87 | status_type = self.arguments.pop() 88 | 89 | self.status_defs = self._load_status_file(status_defs_file) 90 | 91 | # LOG.info("%s" % str(self.status_defs)) 92 | 93 | if status_type not in self.status_types: 94 | error = self.state_machine.reporter.error( 95 | 'Type {} is not one of {}'.format( 96 | status_type, self.status_types), 97 | nodes.literal_block(self.block_text, self.block_text), 98 | line=self.lineno) 99 | return [error] 100 | 101 | self.yaml = self._load_codes() 102 | 103 | self.max_cols = len(self.headers) 104 | # TODO(sdague): it would be good to dynamically set column 105 | # widths (or basically make the colwidth thing go away 106 | # entirely) 107 | self.options['widths'] = [30, 70] 108 | self.col_widths = self.get_column_widths(self.max_cols) 109 | if isinstance(self.col_widths, tuple): 110 | # In docutils 0.13.1, get_column_widths returns a (widths, 111 | # colwidths) tuple, where widths is a string (i.e. 'auto'). 112 | # See https://sourceforge.net/p/docutils/patches/120/. 113 | self.col_widths = self.col_widths[1] 114 | # Actually convert the yaml 115 | title, messages = self.make_title() 116 | # LOG.info("Title %s, messages %s" % (title, messages)) 117 | table_node = self.build_table() 118 | self.add_name(table_node) 119 | 120 | title_block = nodes.title( 121 | text=status_type.capitalize()) 122 | 123 | section = nodes.section(ids=title_block) 124 | section += title_block 125 | section += table_node 126 | 127 | return [section] + messages 128 | 129 | def _load_codes(self): 130 | content = "\n".join(self.content) 131 | parsed = yaml.safe_load(content) 132 | 133 | new_content = list() 134 | 135 | for item in parsed: 136 | if isinstance(item, int): 137 | new_content.append((item, self.status_defs[item]['default'])) 138 | else: 139 | try: 140 | for code, reason in item.items(): 141 | new_content.append( 142 | (code, self.status_defs[code][reason]) 143 | ) 144 | except KeyError: 145 | LOG.warning( 146 | "Could not find {} for code {}".format(reason, code)) 147 | new_content.append( 148 | (code, self.status_defs[code]['default'])) 149 | 150 | return new_content 151 | 152 | def build_table(self): 153 | table = nodes.table() 154 | tgroup = nodes.tgroup(cols=len(self.headers)) 155 | table += tgroup 156 | 157 | # TODO(sdague): it would be really nice to figure out how not 158 | # to have this stanza, it kind of messes up all of the table 159 | # formatting because it doesn't let tables just be the right 160 | # size. 161 | tgroup.extend( 162 | nodes.colspec(colwidth=col_width, colname='c' + str(idx)) 163 | for idx, col_width in enumerate(self.col_widths) 164 | ) 165 | 166 | thead = nodes.thead() 167 | tgroup += thead 168 | 169 | row_node = nodes.row() 170 | thead += row_node 171 | row_node.extend(nodes.entry(h, nodes.paragraph(text=h)) 172 | for h in self.headers) 173 | 174 | tbody = nodes.tbody() 175 | tgroup += tbody 176 | 177 | rows, groups = self.collect_rows() 178 | tbody.extend(rows) 179 | table.extend(groups) 180 | 181 | return table 182 | 183 | def add_col(self, node): 184 | entry = nodes.entry() 185 | entry.append(node) 186 | return entry 187 | 188 | def add_desc_col(self, value): 189 | entry = nodes.entry() 190 | result = ViewList(value.split('\n')) 191 | self.state.nested_parse(result, 0, entry) 192 | return entry 193 | 194 | def collect_rows(self): 195 | rows = [] 196 | groups = [] 197 | try: 198 | # LOG.info("Parsed content is: %s" % self.yaml) 199 | for code, desc in self.yaml: 200 | 201 | h_code = http_code() 202 | h_code['code'] = code 203 | h_code['title'] = self.CODES.get(code, 'Unknown') 204 | 205 | trow = nodes.row() 206 | trow += self.add_col(h_code) 207 | trow += self.add_desc_col(desc) 208 | rows.append(trow) 209 | except AttributeError as exc: 210 | # if 'key' in locals(): 211 | LOG.warning("Failure on key: %s, values: %s. %s" % 212 | (code, desc, exc)) 213 | # else: 214 | # rows.append(self.show_no_yaml_error()) 215 | return rows, groups 216 | 217 | 218 | def http_code_html(self, node): 219 | tmpl = "%(code)s - %(title)s" 220 | self.body.append(tmpl % node) 221 | raise nodes.SkipNode 222 | 223 | 224 | def http_code_text(self, node): 225 | raise nodes.SkipNode 226 | 227 | 228 | class http_code(nodes.Part, nodes.Element): 229 | """Node for http_code stanza 230 | 231 | Because we need to insert very specific HTML at the final stage of 232 | processing, the http_code stanza needs a custom node type. This 233 | lets us accumulate the relevant data into this node, during 234 | parsing, but not turn it into known sphinx types (lists, tables, 235 | sections). 236 | 237 | Then, during the final build phase we transform directly to the 238 | html that we want. 239 | 240 | NOTE: this means we error trying to build latex or man pages for 241 | these stanza types right now. This is all fixable if we add an 242 | output formatter for this node type, but it's not yet a 243 | priority. Contributions welcomed. 244 | """ 245 | pass 246 | -------------------------------------------------------------------------------- /os_api_ref/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/os-api-ref/6084245033f4910e7bc8437cac0d76c2087c88f9/os_api_ref/tests/__init__.py -------------------------------------------------------------------------------- /os_api_ref/tests/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010-2011 OpenStack Foundation 2 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | import pathlib 18 | import shutil 19 | 20 | import fixtures 21 | import tempfile 22 | import testtools 23 | 24 | from sphinx.testing.util import SphinxTestApp 25 | 26 | 27 | def example_dir(name=""): 28 | return os.path.join(os.path.dirname(__file__), 'examples', name) 29 | 30 | 31 | _TRUE_VALUES = ('True', 'true', '1', 'yes') 32 | 33 | 34 | class with_app: 35 | def __init__(self, **kwargs): 36 | self.srcdir = pathlib.Path(kwargs['srcdir']) 37 | self.sphinx_app_args = kwargs 38 | 39 | def __call__(self, f): 40 | def newf(*args, **kwargs): 41 | with tempfile.TemporaryDirectory() as tmpdirname: 42 | tmpdir = pathlib.Path(tmpdirname) 43 | tmproot = tmpdir.joinpath(self.srcdir.name) 44 | shutil.copytree(self.srcdir, tmproot) 45 | self.sphinx_app_args['srcdir'] = tmproot 46 | self.builddir = tmproot.joinpath('_build') 47 | 48 | app = SphinxTestApp(freshenv=True, **self.sphinx_app_args) 49 | 50 | f(*args, app, app._status, app._warning, **kwargs) 51 | 52 | app.cleanup() 53 | return newf 54 | 55 | 56 | class OutputStreamCapture(fixtures.Fixture): 57 | """Capture output streams during tests. 58 | 59 | This fixture captures errant printing to stderr / stdout during 60 | the tests and lets us see those streams at the end of the test 61 | runs instead. Useful to see what was happening during failed 62 | tests. 63 | """ 64 | def setUp(self): 65 | super().setUp() 66 | if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES: 67 | self.out = self.useFixture(fixtures.StringStream('stdout')) 68 | self.useFixture( 69 | fixtures.MonkeyPatch('sys.stdout', self.out.stream)) 70 | if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES: 71 | self.err = self.useFixture(fixtures.StringStream('stderr')) 72 | self.useFixture( 73 | fixtures.MonkeyPatch('sys.stderr', self.err.stream)) 74 | 75 | @property 76 | def stderr(self): 77 | return self.err._details["stderr"].as_text() 78 | 79 | @property 80 | def stdout(self): 81 | return self.out._details["stdout"].as_text() 82 | 83 | 84 | class Timeout(fixtures.Fixture): 85 | """Setup per test timeouts. 86 | 87 | In order to avoid test deadlocks we support setting up a test 88 | timeout parameter read from the environment. In almost all 89 | cases where the timeout is reached this means a deadlock. 90 | 91 | A class level TIMEOUT_SCALING_FACTOR also exists, which allows 92 | extremely long tests to specify they need more time. 93 | """ 94 | 95 | def __init__(self, timeout, scaling=1): 96 | super().__init__() 97 | try: 98 | self.test_timeout = int(timeout) 99 | except ValueError: 100 | # If timeout value is invalid do not set a timeout. 101 | self.test_timeout = 0 102 | if scaling >= 1: 103 | self.test_timeout *= scaling 104 | else: 105 | raise ValueError('scaling value must be >= 1') 106 | 107 | def setUp(self): 108 | super().setUp() 109 | if self.test_timeout > 0: 110 | self.useFixture(fixtures.Timeout(self.test_timeout, gentle=True)) 111 | 112 | 113 | class TestCase(testtools.TestCase): 114 | 115 | """Test case base class for all unit tests.""" 116 | 117 | def setUp(self): 118 | """Run before each test method to initialize test environment.""" 119 | super().setUp() 120 | self.useFixture(Timeout( 121 | os.environ.get('OS_TEST_TIMEOUT', 0))) 122 | self.useFixture(OutputStreamCapture()) 123 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/basic/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | # -- General configuration ---------------------------------------------------- 15 | 16 | # Add any Sphinx extension module names here, as strings. They can be 17 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 18 | 19 | import openstackdocstheme 20 | 21 | html_theme = 'openstackdocs' 22 | html_theme_path = [openstackdocstheme.get_html_theme_path()] 23 | html_theme_options = { 24 | "sidebar_mode": "toc", 25 | } 26 | 27 | extensions = [ 28 | 'os_api_ref', 29 | ] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The master toctree document. 35 | master_doc = 'index' 36 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/basic/index.rst: -------------------------------------------------------------------------------- 1 | .. rest_expand_all:: 2 | 3 | I am text, hear me roar! 4 | 5 | ============== 6 | List Servers 7 | ============== 8 | 9 | .. rest_method:: GET /servers 10 | 11 | .. rest_parameters:: parameters.yaml 12 | 13 | - name: name 14 | 15 | Response codes 16 | -------------- 17 | 18 | .. rest_status_code:: success status.yaml 19 | 20 | - 200 21 | - 100 22 | - 201 23 | 24 | 25 | .. rest_status_code:: error status.yaml 26 | 27 | - 405 28 | - 403 29 | - 401 30 | - 400 31 | - 500 32 | - 409: duplcate_zone 33 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/basic/parameters.yaml: -------------------------------------------------------------------------------- 1 | name: 2 | in: body 3 | required: true 4 | type: string 5 | description: | 6 | The name of things 7 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/basic/status.yaml: -------------------------------------------------------------------------------- 1 | ################# 2 | # Success Codes # 3 | ################# 4 | 100: 5 | default: | 6 | An unusual code for an API 7 | 200: 8 | default: | 9 | Request was successful. 10 | 201: 11 | default: | 12 | Resource was created and is ready to use. 13 | 14 | ################# 15 | # Error Codes # 16 | ################# 17 | 18 | 400: 19 | default: | 20 | Some content in the request was invalid 21 | zone_data_error: | 22 | Some of the data for the 23 | 401: 24 | default: | 25 | User must authenticate before making a request 26 | 403: 27 | default: | 28 | Policy does not allow current user to do this operation. 29 | 405: 30 | default: | 31 | Method is not valid for this endpoint. 32 | 409: 33 | default: | 34 | This operation conflicted with another operation on this resource 35 | duplcate_zone: | 36 | There is already a zone with this name. 37 | 500: 38 | default: | 39 | Something went wrong inside the service. 40 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/microversions/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | # -- General configuration ---------------------------------------------------- 15 | 16 | # Add any Sphinx extension module names here, as strings. They can be 17 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 18 | 19 | import openstackdocstheme 20 | 21 | html_theme = 'openstackdocs' 22 | html_theme_path = [openstackdocstheme.get_html_theme_path()] 23 | html_theme_options = { 24 | "sidebar_mode": "toc", 25 | } 26 | 27 | extensions = [ 28 | 'os_api_ref', 29 | ] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The master toctree document. 35 | master_doc = 'index' 36 | 37 | os_api_ref_min_microversion = '2.1' 38 | os_api_ref_max_microversion = '2.30' 39 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/microversions/index.rst: -------------------------------------------------------------------------------- 1 | .. rest_expand_all:: 2 | 3 | I am text, hear me roar! 4 | 5 | ============== 6 | List Servers 7 | ============== 8 | 9 | .. rest_method:: GET /servers 10 | 11 | .. rest_parameters:: parameters.yaml 12 | 13 | - name: name 14 | - name2: name2 15 | - name3: name3 16 | 17 | =========== 18 | List Tags 19 | =========== 20 | 21 | .. rest_method:: GET /tags 22 | min_version: 2.17 23 | max_version: 2.19 24 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/microversions/parameters.yaml: -------------------------------------------------------------------------------- 1 | name: 2 | in: body 3 | required: true 4 | type: string 5 | description: | 6 | The name of things 7 | name2: 8 | in: body 9 | required: true 10 | type: string 11 | description: | 12 | The name of things 13 | min_version: 2.11 14 | name3: 15 | in: body 16 | required: true 17 | type: string 18 | description: | 19 | The name of things 20 | max_version: 2.20 -------------------------------------------------------------------------------- /os_api_ref/tests/examples/warnings/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 3 | # not use this file except in compliance with the License. You may obtain 4 | # a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # License for the specific language governing permissions and limitations 12 | # under the License. 13 | 14 | # -- General configuration ---------------------------------------------------- 15 | 16 | # Add any Sphinx extension module names here, as strings. They can be 17 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 18 | 19 | import openstackdocstheme 20 | 21 | html_theme = 'openstackdocs' 22 | html_theme_path = [openstackdocstheme.get_html_theme_path()] 23 | html_theme_options = { 24 | "sidebar_mode": "toc", 25 | } 26 | 27 | extensions = [ 28 | 'os_api_ref', 29 | ] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The master toctree document. 35 | master_doc = 'index' 36 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/warnings/empty_parameters_file.yaml: -------------------------------------------------------------------------------- 1 | # Empty parameter file 2 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/warnings/index.rst: -------------------------------------------------------------------------------- 1 | .. rest_expand_all:: 2 | 3 | I am text, hear me roar! 4 | 5 | ============== 6 | List Servers 7 | ============== 8 | 9 | .. rest_method:: GET /servers 10 | 11 | .. rest_parameters:: parameters.yaml 12 | 13 | - name: name 14 | - name: lookup_key_name 15 | - name: name_1 16 | - invalid_name 17 | 18 | 19 | No Parameters Specified 20 | ----------------------- 21 | 22 | .. rest_parameters:: parameters.yaml 23 | 24 | 25 | 26 | Empty File and Parameters Specified 27 | ----------------------------------- 28 | 29 | .. rest_parameters:: empty_parameters_file.yaml 30 | 31 | - name: name 32 | 33 | Nonexistent Parameter File 34 | -------------------------- 35 | 36 | .. rest_parameters:: no_parameters.yaml 37 | 38 | 39 | Check missing path parameters in stanza 40 | --------------------------------------- 41 | 42 | .. rest_method:: GET /server/{server_id}/{new_id}/{new_id2} 43 | 44 | .. rest_parameters:: parameters.yaml 45 | 46 | - server_id: server_id 47 | 48 | Check another missing path parameters in stanza 49 | ----------------------------------------------- 50 | 51 | .. rest_method:: GET /server/{b_id}/{c_id2}/{server_id} 52 | 53 | .. rest_parameters:: parameters.yaml 54 | 55 | - server_id: server_id 56 | -------------------------------------------------------------------------------- /os_api_ref/tests/examples/warnings/parameters.yaml: -------------------------------------------------------------------------------- 1 | # valid path parameter 2 | server_id: 3 | description: | 4 | ID for server. 5 | in: path 6 | required: true 7 | type: string 8 | # These are out of order, this should be a warning. 9 | name2: 10 | in: body 11 | required: false 12 | type: string 13 | description: | 14 | foo 15 | name: 16 | in: body 17 | required: true 18 | type: string 19 | description: | 20 | The name of things 21 | name_1: 22 | description: | 23 | name_1 is missing type field. 24 | in: body 25 | required: true 26 | -------------------------------------------------------------------------------- /os_api_ref/tests/test_basic_example.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """ 14 | test_os_api_ref 15 | ---------------------------------- 16 | 17 | Tests for `os_api_ref` module. 18 | """ 19 | 20 | from bs4 import BeautifulSoup 21 | 22 | from os_api_ref.tests import base 23 | 24 | 25 | class TestBasicExample(base.TestCase): 26 | """Test basic rendering. 27 | 28 | This can be used to test that basic rendering works for these 29 | examples, so if someone breaks something we know. 30 | """ 31 | 32 | @base.with_app(buildername='html', srcdir=base.example_dir('basic')) 33 | def setUp(self, app, status, warning): 34 | super().setUp() 35 | self.app = app 36 | self.status = status 37 | self.warning = warning 38 | self.app.build() 39 | self.html = (app.outdir / 'index.html').read_text(encoding='utf-8') 40 | self.soup = BeautifulSoup(self.html, 'html.parser') 41 | self.content = str(self.soup) 42 | 43 | def test_expand_all(self): 44 | """Do we get an expand all button like we expect.""" 45 | content = str(self.soup.find(id='expand-all')) 46 | example_button = ('') 49 | self.assertEqual( 50 | example_button, 51 | content) 52 | 53 | def test_rest_method(self): 54 | """Do we get a REST method call block""" 55 | 56 | # TODO(sdague): it probably would make sense to do this as a 57 | # whole template instead of parts. 58 | content = str(self.soup.find_all(class_='operation-grp')) 59 | self.assertIn( 60 | '', 61 | str(content)) 62 | self.assertIn( 63 | 'GET', 64 | str(content)) 65 | self.assertIn( 66 | '
/servers
', 67 | str(content)) 68 | self.assertIn( 69 | (''), 72 | str(content)) 73 | 74 | def test_parameters(self): 75 | """Do we get some parameters table""" 76 | table = """ 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |

Name

In

Type

Description

name

body

string

The name of things

""" 92 | self.assertIn(table, self.content) 93 | 94 | def test_rest_response(self): 95 | success_table = """ 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |

Code

Reason

200 - OK

Request was successful.

100 - Continue

An unusual code for an API

201 - Created

Resource was created and is ready to use.

""" 113 | 114 | error_table = """ 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |

Code

Reason

405 - Method Not Allowed

Method is not valid for this endpoint.

403 - Forbidden

Policy does not allow current user to do this operation.

401 - Unauthorized

User must authenticate before making a request

400 - Bad Request

Some content in the request was invalid

500 - Internal Server Error

Something went wrong inside the service.

409 - Conflict

There is already a zone with this name.

""" 141 | 142 | self.assertIn(success_table, self.content) 143 | self.assertIn(error_table, self.content) 144 | -------------------------------------------------------------------------------- /os_api_ref/tests/test_microversions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """ 14 | test_os_api_ref 15 | ---------------------------------- 16 | 17 | Tests for `os_api_ref` module. 18 | """ 19 | 20 | from bs4 import BeautifulSoup 21 | 22 | from os_api_ref.tests import base 23 | 24 | 25 | class TestMicroversions(base.TestCase): 26 | """Test basic rendering. 27 | 28 | This can be used to test that basic rendering works for these 29 | examples, so if someone breaks something we know. 30 | """ 31 | 32 | @base.with_app(buildername='html', 33 | srcdir=base.example_dir('microversions')) 34 | def setUp(self, app, status, warning): 35 | super().setUp() 36 | self.app = app 37 | self.app.build() 38 | self.status = status.getvalue() 39 | self.warning = warning.getvalue() 40 | self.html = (app.outdir / 'index.html').read_text(encoding='utf-8') 41 | self.soup = BeautifulSoup(self.html, 'html.parser') 42 | self.content = str(self.soup) 43 | 44 | def test_rest_method(self): 45 | """Test that min / max mv css class attributes are set""" 46 | content = self.soup.find_all(class_='rp_min_ver_2_17') 47 | self.assertRegex( 48 | str(content[0]), 49 | '^
61 | 62 |

Name

63 |

In

64 |

Type

65 |

Description

66 | 67 | 68 | 69 |

name

70 |

body

71 |

string

72 |

The name of things

73 | 74 |

name2

75 |

body

76 |

string

77 |

The name of things

78 |

New in version 2.11

79 | 80 | 81 |

name3

82 |

body

83 |

string

84 |

The name of things

85 |

Available until version 2.20

86 | 87 | 88 | 89 | 90 | """ 91 | self.assertIn(table, self.content) 92 | 93 | def test_mv_selector(self): 94 | 95 | button_selectors = '' # noqa 96 | self.assertIn(button_selectors, self.content) 97 | 98 | def test_js_declares(self): 99 | self.assertIn("os_max_mv = 30;", self.content) 100 | self.assertIn("os_min_mv = 1;", self.content) 101 | -------------------------------------------------------------------------------- /os_api_ref/tests/test_os_api_ref.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """ 14 | test_os_api_ref 15 | ---------------------------------- 16 | 17 | Tests for `os_api_ref` module. 18 | """ 19 | 20 | from os_api_ref.tests import base 21 | 22 | 23 | class TestOs_api_ref(base.TestCase): 24 | 25 | def test_something(self): 26 | pass 27 | -------------------------------------------------------------------------------- /os_api_ref/tests/test_warnings.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | """ 14 | test_os_api_ref 15 | ---------------------------------- 16 | 17 | Tests for `os_api_ref` module. 18 | """ 19 | 20 | from collections import OrderedDict 21 | 22 | from bs4 import BeautifulSoup 23 | 24 | from os_api_ref.tests import base 25 | 26 | 27 | class TestWarnings(base.TestCase): 28 | """Test basic rendering. 29 | 30 | This can be used to test that basic rendering works for these 31 | examples, so if someone breaks something we know. 32 | """ 33 | 34 | @base.with_app(buildername='html', srcdir=base.example_dir('warnings')) 35 | def setUp(self, app, status, warning): 36 | super().setUp() 37 | self.app = app 38 | self.app.build() 39 | self.status = status.getvalue() 40 | self.warning = warning.getvalue() 41 | self.html = (app.outdir / 'index.html').read_text(encoding='utf-8') 42 | self.soup = BeautifulSoup(self.html, 'html.parser') 43 | self.content = str(self.soup) 44 | 45 | def test_out_of_order(self): 46 | """Do we get an out of order naming warning.""" 47 | self.assertIn( 48 | ("WARNING: Parameters out of order ``name2`` " 49 | "should be after ``name``"), 50 | self.warning) 51 | 52 | def test_missing_lookup_name(self): 53 | """Warning when missing a lookup key in parameter file.""" 54 | self.assertIn( 55 | ("WARNING: No field definition for ``lookup_key_name`` found in "), 56 | self.warning) 57 | 58 | def test_missing_field(self): 59 | """Warning when missing type field in parameter file.""" 60 | # py312 changes string interpretation of OrderedDict. 61 | # Prevent such failures by using OrderedDict directly 62 | cmp_data = OrderedDict({ 63 | "description": "name_1 is missing type field.\n", 64 | "in": "body", 65 | "required": True 66 | }) 67 | self.assertIn( 68 | ("WARNING: Failure on key: name, values: " + 69 | f"{cmp_data}. " + 70 | "'NoneType' object has no attribute 'split'"), 71 | self.warning) 72 | 73 | def test_invalid_parameter_definition(self): 74 | """Warning when parameter definition is invalid.""" 75 | self.assertIn( 76 | ("WARNING: Invalid parameter definition ``invalid_name``. " + 77 | "Expected format: ``name: reference``. "), 78 | self.warning) 79 | 80 | def test_empty_parameter_file(self): 81 | """Warning when parameter file exists but is empty.""" 82 | self.assertIn( 83 | ("WARNING: Parameters file is empty"), 84 | self.warning) 85 | 86 | def test_no_parameters_set(self): 87 | """Error when parameters are not set in rest_parameters stanza.""" 88 | self.assertIn( 89 | ("No parameters defined\n\n.." + 90 | " rest_parameters:: parameters.yaml"), 91 | self.warning) 92 | 93 | def test_parameter_file_not_exist(self): 94 | """Error when parameter file does not exist""" 95 | self.assertIn( 96 | ("No parameters defined\n\n.." + 97 | " rest_parameters:: no_parameters.yaml"), 98 | self.warning) 99 | 100 | def test_missing_path_parameter_in_stanza(self): 101 | """Warning when path param not found in rest_parameter stanza.""" 102 | 103 | self.assertIn( 104 | ("WARNING: No path parameter ``b_id`` found in" + 105 | " rest_parameter stanza.\n"), 106 | self.warning) 107 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | pbr!=2.1.0,>=2.0.0 # Apache-2.0 6 | PyYAML>=3.12 # MIT 7 | sphinx>=4.0.0 # BSD 8 | openstackdocstheme>=2.2.1 # Apache-2.0 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = os-api-ref 3 | summary = Sphinx Extensions to support API reference sites in OpenStack 4 | description_file = 5 | README.rst 6 | author = OpenStack 7 | author_email = openstack-discuss@lists.openstack.org 8 | home_page = https://docs.openstack.org/os-api-ref/latest/ 9 | python_requires = >=3.9 10 | classifier = 11 | Environment :: OpenStack 12 | Intended Audience :: Information Technology 13 | Intended Audience :: System Administrators 14 | License :: OSI Approved :: Apache Software License 15 | Operating System :: POSIX :: Linux 16 | Programming Language :: Python 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.9 19 | Programming Language :: Python :: 3.10 20 | Programming Language :: Python :: 3.11 21 | Programming Language :: Python :: 3.12 22 | Programming Language :: Python :: 3 :: Only 23 | Programming Language :: Python :: Implementation :: CPython 24 | 25 | 26 | [files] 27 | packages = 28 | os_api_ref 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT 17 | import setuptools 18 | 19 | # In python < 2.7.4, a lazy loading of package `pbr` will break 20 | # setuptools if some other modules registered functions in `atexit`. 21 | # solution from: http://bugs.python.org/issue15881#msg170215 22 | try: 23 | import multiprocessing # noqa 24 | except ImportError: 25 | pass 26 | 27 | setuptools.setup( 28 | setup_requires=['pbr>=2.0.0'], 29 | pbr=True) 30 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | coverage!=4.4,>=4.0 # Apache-2.0 6 | python-subunit>=1.0.0 # Apache-2.0/BSD 7 | testrepository>=0.0.18 # Apache-2.0/BSD 8 | testtools>=2.2.0 # MIT 9 | beautifulsoup4>=4.6.0 # MIT 10 | stestr>=2.0.0 # Apache-2.0 11 | 12 | # The minimum version requirement is specific to unit tests. 13 | sphinx>=7.2.0 # BSD 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.1.1 3 | envlist = py3,pep8,docs 4 | ignore_basepython_conflict = true 5 | skipsdist = true 6 | 7 | [testenv] 8 | basepython = python3 9 | setenv = 10 | VIRTUAL_ENV={envdir} 11 | deps = 12 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 13 | -r{toxinidir}/requirements.txt 14 | -r{toxinidir}/test-requirements.txt 15 | commands = stestr run {posargs} 16 | 17 | [testenv:pep8] 18 | skip_install = true 19 | deps = 20 | pre-commit 21 | commands = 22 | pre-commit run -a 23 | 24 | [testenv:venv] 25 | commands = {posargs} 26 | 27 | [testenv:cover] 28 | setenv = 29 | {[testenv]setenv} 30 | PYTHON=coverage run --source nova --parallel-mode 31 | commands = 32 | coverage erase 33 | stestr run {posargs} 34 | coverage combine 35 | coverage html -d cover 36 | coverage report 37 | 38 | [testenv:docs] 39 | deps = 40 | -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} 41 | -r{toxinidir}/doc/requirements.txt 42 | commands = sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html 43 | 44 | [testenv:debug] 45 | commands = oslo_debug_helper {posargs} 46 | 47 | [flake8] 48 | # E123, E125 skipped as they are invalid PEP-8. 49 | show-source = true 50 | ignore = E123,E125,E129,W504 51 | builtins = _ 52 | exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build 53 | --------------------------------------------------------------------------------