Render the redfish interop profile in the docs
Adds a pretty straightforward Sphinx plugin that reads the JSON profile file and renders it nicely in a document that is then included from the Redfish page. Change-Id: Ic2da61cb510897eac8a2e162816cfd05cc22994c
This commit is contained in:
parent
d28a61b2c0
commit
be5c4b7d63
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@
|
||||
_build
|
||||
doc/source/contributor/api/
|
||||
_static
|
||||
doc/source/admin/drivers/redfish/OpenStackIronicProfile.*.rst
|
||||
|
||||
# release notes build
|
||||
releasenotes/build
|
||||
|
187
doc/source/_exts/redfish_interop.py
Normal file
187
doc/source/_exts/redfish_interop.py
Normal file
@ -0,0 +1,187 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
|
||||
# Data model #
|
||||
|
||||
|
||||
class Entity:
|
||||
"""Represents an entity in the profile."""
|
||||
|
||||
def __init__(self, name, src):
|
||||
self.name = name
|
||||
self.src = src
|
||||
self.purpose = src.get('Purpose', '')
|
||||
self.writable = src.get('WriteRequirement') == 'Mandatory'
|
||||
self.required = (src.get('ReadRequirement') in ('Mandatory', None)
|
||||
or self.writable)
|
||||
|
||||
|
||||
class ActionParameter(Entity):
|
||||
"""Represents a parameter in an Action."""
|
||||
|
||||
def __init__(self, name, src):
|
||||
super().__init__(name, src)
|
||||
self.required_values = src.get('ParameterValues') or []
|
||||
self.recommended_values = src.get('RecommendedValues') or []
|
||||
|
||||
|
||||
class Action(Entity):
|
||||
"""Represents an action on a resource."""
|
||||
|
||||
def __init__(self, name, src):
|
||||
super().__init__(name, src)
|
||||
self.parameters = {
|
||||
name: ActionParameter(name, value)
|
||||
for name, value in src.get('Parameters', {}).items()
|
||||
}
|
||||
|
||||
|
||||
class Resource(Entity):
|
||||
"""Represents any resource in the profile.
|
||||
|
||||
Both top-level resources and nested fields are represented by this class
|
||||
(but actions are not).
|
||||
"""
|
||||
|
||||
def __init__(self, name, src):
|
||||
super().__init__(name, src)
|
||||
self.min_support_values = src.get('MinSupportValues')
|
||||
self.properties = {
|
||||
name: Resource(name, value)
|
||||
for name, value in src.get('PropertyRequirements', {}).items()
|
||||
}
|
||||
self.actions = {
|
||||
name: Action(name, value)
|
||||
for name, value in src.get('ActionRequirements', {}).items()
|
||||
}
|
||||
self.link_to = (src['Values'][0]
|
||||
if src.get('Comparison') == 'LinkToResource'
|
||||
else None)
|
||||
|
||||
|
||||
# Rendering #
|
||||
|
||||
LEVELS = {0: '=', 1: '-', 2: '~', 3: '^'}
|
||||
INDENT = ' ' * 4
|
||||
|
||||
|
||||
class NestedWriter:
|
||||
"""A writer that is nested with indentations."""
|
||||
|
||||
def __init__(self, dest, level=0):
|
||||
self.dest = dest
|
||||
self.level = level
|
||||
|
||||
def text(self, text):
|
||||
print(INDENT * self.level + text, file=self.dest)
|
||||
|
||||
def para(self, text):
|
||||
self.text(text)
|
||||
print(file=self.dest)
|
||||
|
||||
def _nested_common(self, res):
|
||||
required = " **[required]**" if res.required else ""
|
||||
writable = " **[writable]**" if res.writable else ""
|
||||
self.text(f"``{res.name}``{required}{writable}")
|
||||
nested = NestedWriter(self.dest, self.level + 1)
|
||||
if res.purpose:
|
||||
nested.para(res.purpose)
|
||||
return nested
|
||||
|
||||
def action(self, res):
|
||||
nested = self._nested_common(res)
|
||||
for prop in res.parameters.values():
|
||||
nested.action_parameter(prop)
|
||||
print(file=self.dest)
|
||||
|
||||
def action_parameter(self, res):
|
||||
self._nested_common(res)
|
||||
print(file=self.dest)
|
||||
|
||||
def resource(self, res):
|
||||
nested = self._nested_common(res)
|
||||
for prop in res.properties.values():
|
||||
nested.resource(prop)
|
||||
if res.link_to:
|
||||
# NOTE(dtantsur): this is a bit hacky, but we don't have
|
||||
# definitions for all possible collections.
|
||||
split = res.link_to.split('Collection')
|
||||
if len(split) > 1:
|
||||
nested.text("Link to a collection of "
|
||||
f":ref:`Redfish-{split[0]}` resources.")
|
||||
else:
|
||||
nested.text(f"Link to a :ref:`Redfish-{res.link_to}` "
|
||||
"resource.")
|
||||
|
||||
print(file=self.dest)
|
||||
|
||||
|
||||
class Writer(NestedWriter):
|
||||
|
||||
def __init__(self, dest):
|
||||
super().__init__(dest)
|
||||
|
||||
def title(self, text, level=1):
|
||||
print(text, file=self.dest)
|
||||
print(LEVELS[level] * len(text), file=self.dest)
|
||||
|
||||
def top_level(self, res):
|
||||
required = " **[required]**" if res.required else ""
|
||||
self.para(f".. _Redfish-{res.name}:")
|
||||
self.title(f"{res.name}")
|
||||
self.para(f"{res.purpose}{required}")
|
||||
if res.properties:
|
||||
self.title("Properties", level=2)
|
||||
for name, prop in res.properties.items():
|
||||
self.resource(prop)
|
||||
if res.actions:
|
||||
self.title("Actions", level=2)
|
||||
for name, act in res.actions.items():
|
||||
self.action(act)
|
||||
|
||||
|
||||
def builder_inited(app: Sphinx):
|
||||
source = os.path.join(app.srcdir, app.config.redfish_interop_source)
|
||||
with open(source) as fp:
|
||||
profile = json.load(fp)
|
||||
fname = os.path.basename(source).replace('json', 'rst')
|
||||
dstdir = os.path.join(app.srcdir, app.config.redfish_interop_output_dir)
|
||||
with open(os.path.join(dstdir, fname), 'wt') as dest:
|
||||
w = Writer(dest)
|
||||
w.title(f"{profile['ProfileName']} {profile['ProfileVersion']}", 0)
|
||||
w.para(profile['Purpose'])
|
||||
|
||||
try:
|
||||
for name, value in sorted(
|
||||
(name, value)
|
||||
for name, value in profile['Resources'].items()
|
||||
):
|
||||
w.top_level(Resource(name, value))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
|
||||
def setup(app: Sphinx):
|
||||
app.connect('builder-inited', builder_inited)
|
||||
app.add_config_value('redfish_interop_source', None, 'env', [str])
|
||||
app.add_config_value('redfish_interop_output_dir', None, 'env', [str])
|
||||
return {'version': __version__}
|
@ -18,6 +18,10 @@ and conformance testing. Many of the properties defined within this structure
|
||||
have assumed default values that correspond with the most common use case, so
|
||||
that those properties can be omitted from the document for brevity.
|
||||
|
||||
.. toctree::
|
||||
|
||||
OpenStackIronicProfile.v1_1_0
|
||||
|
||||
Validation of Profiles using DMTF tool
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -27,4 +31,3 @@ Redfish Interoperability Profile. The Redfish Interop Validator is available
|
||||
for download from the DMTF's organization on Github at
|
||||
https://github.com/DMTF/Redfish-Interop-Validator. Refer to instructions in
|
||||
README on how to configure and run validation.
|
||||
|
||||
|
@ -42,7 +42,8 @@ extensions = ['sphinx.ext.viewcode',
|
||||
'oslo_policy.sphinxpolicygen',
|
||||
'automated_steps',
|
||||
'openstackdocstheme',
|
||||
'web_api_docstring'
|
||||
'web_api_docstring',
|
||||
'redfish_interop',
|
||||
]
|
||||
|
||||
# sphinxcontrib.apidoc options
|
||||
@ -61,6 +62,10 @@ autodoc_default_options = {
|
||||
'special-members': '__call__',
|
||||
}
|
||||
|
||||
redfish_interop_source = \
|
||||
'../../redfish-interop-profiles/OpenStackIronicProfile.v1_1_0.json'
|
||||
redfish_interop_output_dir = 'admin/drivers/redfish/'
|
||||
|
||||
openstackdocs_repo_name = 'openstack/ironic'
|
||||
openstackdocs_use_storyboard = False
|
||||
openstackdocs_pdf_link = True
|
||||
|
Loading…
Reference in New Issue
Block a user