Replace support matrix ext with common library
The code to generate a support matrix has been pulled into a common library. Using this instead of duplicating code in various projects that need it. Change-Id: Ib15c086ceb84029e96be0fc60bd64f2a7a3e41aa Co-Authored-By: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
6828a01eac
commit
9575937e56
@ -1,513 +0,0 @@
|
|||||||
# Copyright (C) 2014 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Feature support matrix extension for Sphinx.
|
|
||||||
|
|
||||||
This provides a sphinx extension able to render support matrix INI files into
|
|
||||||
the developer documentation.
|
|
||||||
|
|
||||||
It is used via a single directive in the .rst file
|
|
||||||
|
|
||||||
.. support_matrix::
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from six.moves import configparser
|
|
||||||
|
|
||||||
from docutils import nodes
|
|
||||||
from docutils.parsers import rst
|
|
||||||
|
|
||||||
|
|
||||||
class SupportMatrix(object):
|
|
||||||
"""Represent the entire support matrix."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# List of SupportMatrixFeature instances, describing
|
|
||||||
# all the features present in Nova virt drivers
|
|
||||||
self.features = []
|
|
||||||
|
|
||||||
# Dict of (name, SupportMatrixTarget) enumerating
|
|
||||||
# all the hypervisor drivers that have data recorded
|
|
||||||
# for them in self.features. The 'name' dict key is
|
|
||||||
# the value from the SupportMatrixTarget.key attribute
|
|
||||||
self.targets = {}
|
|
||||||
|
|
||||||
|
|
||||||
class SupportMatrixFeature(object):
|
|
||||||
|
|
||||||
STATUS_MANDATORY = "mandatory"
|
|
||||||
STATUS_CHOICE = "choice"
|
|
||||||
STATUS_CONDITION = "condition"
|
|
||||||
STATUS_OPTIONAL = "optional"
|
|
||||||
|
|
||||||
STATUS_ALL = [STATUS_MANDATORY, STATUS_CHOICE,
|
|
||||||
STATUS_CONDITION, STATUS_OPTIONAL]
|
|
||||||
|
|
||||||
def __init__(self, key, title, status=STATUS_OPTIONAL,
|
|
||||||
group=None, notes=None, cli=None):
|
|
||||||
if cli is None:
|
|
||||||
cli = list()
|
|
||||||
# A unique key (eg 'foo.bar.wizz') to identify the feature
|
|
||||||
self.key = key
|
|
||||||
# A human friendly short title for the feature
|
|
||||||
self.title = title
|
|
||||||
# One of the status constants
|
|
||||||
self.status = status
|
|
||||||
# Detail string if status was choice/condition
|
|
||||||
self.group = group
|
|
||||||
# Arbitrarily long string describing the feature in detail
|
|
||||||
self.notes = notes
|
|
||||||
# Dict of (name, SupportMatrixImplementation) detailing
|
|
||||||
# the implementation for each hypervisor driver. The
|
|
||||||
# 'name' dict key is the value from SupportMatrixTarget.key
|
|
||||||
# for the hypervisor in question
|
|
||||||
self.implementations = {}
|
|
||||||
# A list of CLI commands which are related to that feature
|
|
||||||
self.cli = cli
|
|
||||||
|
|
||||||
|
|
||||||
class SupportMatrixImplementation(object):
|
|
||||||
|
|
||||||
STATUS_COMPLETE = "complete"
|
|
||||||
STATUS_PARTIAL = "partial"
|
|
||||||
STATUS_MISSING = "missing"
|
|
||||||
STATUS_UKNOWN = "unknown"
|
|
||||||
|
|
||||||
STATUS_ALL = [STATUS_COMPLETE, STATUS_PARTIAL, STATUS_MISSING,
|
|
||||||
STATUS_UKNOWN]
|
|
||||||
|
|
||||||
def __init__(self, status=STATUS_MISSING, notes=None):
|
|
||||||
# One of the status constants detailing the implementation
|
|
||||||
# level
|
|
||||||
self.status = status
|
|
||||||
# Arbitrary string describing any caveats of the implementation.
|
|
||||||
# Mandatory if status is 'partial', optional otherwise.
|
|
||||||
self.notes = notes
|
|
||||||
|
|
||||||
|
|
||||||
class SupportMatrixTarget(object):
|
|
||||||
|
|
||||||
def __init__(self, key, title, driver, hypervisor=None, architecture=None):
|
|
||||||
"""Initialize a SupportMatrixTarget instance.
|
|
||||||
|
|
||||||
:param key: Unique identifier for the hypervisor driver
|
|
||||||
:param title: Human friendly name of the hypervisor
|
|
||||||
:param driver: Name of the Nova driver
|
|
||||||
:param hypervisor: (optional) Name of the hypervisor, if many
|
|
||||||
:param architecture: (optional) Name of the architecture, if many
|
|
||||||
"""
|
|
||||||
self.key = key
|
|
||||||
self.title = title
|
|
||||||
self.driver = driver
|
|
||||||
self.hypervisor = hypervisor
|
|
||||||
self.architecture = architecture
|
|
||||||
|
|
||||||
|
|
||||||
class SupportMatrixDirective(rst.Directive):
|
|
||||||
|
|
||||||
# The argument is the filename, e.g. support-matrix.ini
|
|
||||||
required_arguments = 1
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
matrix = self._load_support_matrix()
|
|
||||||
return self._build_markup(matrix)
|
|
||||||
|
|
||||||
def _load_support_matrix(self):
|
|
||||||
"""Load a support matrix from a configuration file.
|
|
||||||
|
|
||||||
Reads the support-matrix.ini file and populates an instance of the
|
|
||||||
SupportMatrix class with all the data.
|
|
||||||
|
|
||||||
:returns: SupportMatrix instance
|
|
||||||
"""
|
|
||||||
cfg = configparser.ConfigParser()
|
|
||||||
env = self.state.document.settings.env
|
|
||||||
fname = self.arguments[0]
|
|
||||||
rel_fpath, fpath = env.relfn2path(fname)
|
|
||||||
with open(fpath) as fp:
|
|
||||||
cfg.readfp(fp)
|
|
||||||
|
|
||||||
# This ensures that the docs are rebuilt whenever the
|
|
||||||
# .ini file changes
|
|
||||||
env.note_dependency(rel_fpath)
|
|
||||||
|
|
||||||
matrix = SupportMatrix()
|
|
||||||
matrix.targets = self._get_targets(cfg)
|
|
||||||
matrix.features = self._get_features(cfg, matrix.targets)
|
|
||||||
|
|
||||||
return matrix
|
|
||||||
|
|
||||||
def _get_targets(self, cfg):
|
|
||||||
# The 'targets' section is special - it lists all the
|
|
||||||
# hypervisors that this file records data for
|
|
||||||
|
|
||||||
targets = {}
|
|
||||||
|
|
||||||
for item in cfg.options("targets"):
|
|
||||||
if not item.startswith("driver-impl-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# The driver string will optionally contain
|
|
||||||
# a hypervisor and architecture qualifier
|
|
||||||
# so we expect between 1 and 3 components
|
|
||||||
# in the name
|
|
||||||
key = item[12:]
|
|
||||||
title = cfg.get("targets", item)
|
|
||||||
name = key.split("-")
|
|
||||||
if len(name) == 1:
|
|
||||||
target = SupportMatrixTarget(key,
|
|
||||||
title,
|
|
||||||
name[0])
|
|
||||||
elif len(name) == 2:
|
|
||||||
target = SupportMatrixTarget(key,
|
|
||||||
title,
|
|
||||||
name[0],
|
|
||||||
name[1])
|
|
||||||
elif len(name) == 3:
|
|
||||||
target = SupportMatrixTarget(key,
|
|
||||||
title,
|
|
||||||
name[0],
|
|
||||||
name[1],
|
|
||||||
name[2])
|
|
||||||
else:
|
|
||||||
raise Exception("'%s' field is malformed in '[%s]' section" %
|
|
||||||
(item, "DEFAULT"))
|
|
||||||
|
|
||||||
targets[key] = target
|
|
||||||
|
|
||||||
return targets
|
|
||||||
|
|
||||||
def _get_features(self, cfg, targets):
|
|
||||||
# All sections except 'targets' describe some feature of
|
|
||||||
# the Nova hypervisor driver implementation
|
|
||||||
|
|
||||||
features = []
|
|
||||||
|
|
||||||
for section in cfg.sections():
|
|
||||||
if section == "targets":
|
|
||||||
continue
|
|
||||||
if not cfg.has_option(section, "title"):
|
|
||||||
raise Exception(
|
|
||||||
"'title' field missing in '[%s]' section" % section)
|
|
||||||
|
|
||||||
title = cfg.get(section, "title")
|
|
||||||
|
|
||||||
status = SupportMatrixFeature.STATUS_OPTIONAL
|
|
||||||
if cfg.has_option(section, "status"):
|
|
||||||
# The value is a string "status(group)" where
|
|
||||||
# the 'group' part is optional
|
|
||||||
status = cfg.get(section, "status")
|
|
||||||
offset = status.find("(")
|
|
||||||
group = None
|
|
||||||
if offset != -1:
|
|
||||||
group = status[offset + 1:-1]
|
|
||||||
status = status[0:offset]
|
|
||||||
|
|
||||||
if status not in SupportMatrixFeature.STATUS_ALL:
|
|
||||||
raise Exception(
|
|
||||||
"'status' field value '%s' in ['%s']"
|
|
||||||
"section must be %s" %
|
|
||||||
(status, section,
|
|
||||||
",".join(SupportMatrixFeature.STATUS_ALL)))
|
|
||||||
|
|
||||||
notes = None
|
|
||||||
if cfg.has_option(section, "notes"):
|
|
||||||
notes = cfg.get(section, "notes")
|
|
||||||
cli = []
|
|
||||||
if cfg.has_option(section, "cli"):
|
|
||||||
cli = cfg.get(section, "cli")
|
|
||||||
feature = SupportMatrixFeature(section,
|
|
||||||
title,
|
|
||||||
status,
|
|
||||||
group,
|
|
||||||
notes,
|
|
||||||
cli)
|
|
||||||
|
|
||||||
# Now we've got the basic feature details, we must process
|
|
||||||
# the hypervisor driver implementation for each feature
|
|
||||||
for item in cfg.options(section):
|
|
||||||
if not item.startswith("driver-impl-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
key = item[12:]
|
|
||||||
if key not in targets:
|
|
||||||
raise Exception(
|
|
||||||
"Driver impl '%s' in '[%s]' not declared" %
|
|
||||||
(item, section))
|
|
||||||
|
|
||||||
status = cfg.get(section, item)
|
|
||||||
if status not in SupportMatrixImplementation.STATUS_ALL:
|
|
||||||
raise Exception(
|
|
||||||
"'%s' value '%s' in '[%s]' section must be %s" %
|
|
||||||
(item, status, section,
|
|
||||||
",".join(SupportMatrixImplementation.STATUS_ALL)))
|
|
||||||
|
|
||||||
noteskey = "driver-notes-" + item[12:]
|
|
||||||
notes = None
|
|
||||||
if cfg.has_option(section, noteskey):
|
|
||||||
notes = cfg.get(section, noteskey)
|
|
||||||
|
|
||||||
target = targets[key]
|
|
||||||
impl = SupportMatrixImplementation(status,
|
|
||||||
notes)
|
|
||||||
feature.implementations[target.key] = impl
|
|
||||||
|
|
||||||
for key in targets:
|
|
||||||
if key not in feature.implementations:
|
|
||||||
raise Exception("'%s' missing in '[%s]' section" %
|
|
||||||
(target.key, section))
|
|
||||||
|
|
||||||
features.append(feature)
|
|
||||||
|
|
||||||
return features
|
|
||||||
|
|
||||||
def _build_markup(self, matrix):
|
|
||||||
"""Construct the docutils content for the support matrix."""
|
|
||||||
content = []
|
|
||||||
self._build_summary(matrix, content)
|
|
||||||
self._build_details(matrix, content)
|
|
||||||
# FIXME(dolph): Do not render hardcoded notes that are specific to
|
|
||||||
# Nova.
|
|
||||||
# self._build_notes(content)
|
|
||||||
return content
|
|
||||||
|
|
||||||
def _build_summary(self, matrix, content):
|
|
||||||
"""Construct the docutils content for the support matrix summary.
|
|
||||||
|
|
||||||
The summary consists of a giant table, with one row for each feature,
|
|
||||||
and a column for each hypervisor driver. It provides an 'at a glance'
|
|
||||||
summary of the status of each driver
|
|
||||||
"""
|
|
||||||
summarytitle = nodes.subtitle(text="Summary")
|
|
||||||
summary = nodes.table()
|
|
||||||
cols = len(matrix.targets.keys())
|
|
||||||
cols += 2
|
|
||||||
summarygroup = nodes.tgroup(cols=cols)
|
|
||||||
summarybody = nodes.tbody()
|
|
||||||
summaryhead = nodes.thead()
|
|
||||||
|
|
||||||
for i in range(cols):
|
|
||||||
summarygroup.append(nodes.colspec(colwidth=1))
|
|
||||||
summarygroup.append(summaryhead)
|
|
||||||
summarygroup.append(summarybody)
|
|
||||||
summary.append(summarygroup)
|
|
||||||
content.append(summarytitle)
|
|
||||||
content.append(summary)
|
|
||||||
|
|
||||||
# This sets up all the column headers - two fixed
|
|
||||||
# columns for feature name & status
|
|
||||||
header = nodes.row()
|
|
||||||
blank = nodes.entry()
|
|
||||||
blank.append(nodes.emphasis(text="Feature"))
|
|
||||||
header.append(blank)
|
|
||||||
blank = nodes.entry()
|
|
||||||
blank.append(nodes.emphasis(text="Status"))
|
|
||||||
header.append(blank)
|
|
||||||
summaryhead.append(header)
|
|
||||||
|
|
||||||
# then one column for each hypervisor driver
|
|
||||||
impls = sorted(matrix.targets.keys())
|
|
||||||
for key in impls:
|
|
||||||
target = matrix.targets[key]
|
|
||||||
implcol = nodes.entry()
|
|
||||||
header.append(implcol)
|
|
||||||
implcol.append(nodes.strong(text=target.title))
|
|
||||||
|
|
||||||
# We now produce the body of the table, one row for
|
|
||||||
# each feature to report on
|
|
||||||
for feature in matrix.features:
|
|
||||||
item = nodes.row()
|
|
||||||
|
|
||||||
# the hyperlink target name linking to details
|
|
||||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
|
||||||
feature.key)
|
|
||||||
|
|
||||||
# first the to fixed columns for title/status
|
|
||||||
keycol = nodes.entry()
|
|
||||||
item.append(keycol)
|
|
||||||
keyref = nodes.reference(refid=id)
|
|
||||||
keytxt = nodes.inline()
|
|
||||||
keycol.append(keytxt)
|
|
||||||
keytxt.append(keyref)
|
|
||||||
keyref.append(nodes.strong(text=feature.title))
|
|
||||||
|
|
||||||
statuscol = nodes.entry()
|
|
||||||
item.append(statuscol)
|
|
||||||
statuscol.append(nodes.inline(
|
|
||||||
text=feature.status,
|
|
||||||
classes=["sp_feature_" + feature.status]))
|
|
||||||
|
|
||||||
# and then one column for each hypervisor driver
|
|
||||||
impls = sorted(matrix.targets.keys())
|
|
||||||
for key in impls:
|
|
||||||
target = matrix.targets[key]
|
|
||||||
impl = feature.implementations[key]
|
|
||||||
implcol = nodes.entry()
|
|
||||||
item.append(implcol)
|
|
||||||
|
|
||||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
|
||||||
feature.key + "_" + key)
|
|
||||||
|
|
||||||
implref = nodes.reference(refid=id)
|
|
||||||
impltxt = nodes.inline()
|
|
||||||
implcol.append(impltxt)
|
|
||||||
impltxt.append(implref)
|
|
||||||
|
|
||||||
status = ""
|
|
||||||
if impl.status == SupportMatrixImplementation.STATUS_COMPLETE:
|
|
||||||
status = u"\u2714"
|
|
||||||
elif impl.status == SupportMatrixImplementation.STATUS_MISSING:
|
|
||||||
status = u"\u2716"
|
|
||||||
elif impl.status == SupportMatrixImplementation.STATUS_PARTIAL:
|
|
||||||
status = u"\u2714"
|
|
||||||
elif impl.status == SupportMatrixImplementation.STATUS_UKNOWN:
|
|
||||||
status = u"?"
|
|
||||||
|
|
||||||
implref.append(nodes.literal(
|
|
||||||
text=status,
|
|
||||||
classes=["sp_impl_summary", "sp_impl_" + impl.status]))
|
|
||||||
|
|
||||||
summarybody.append(item)
|
|
||||||
|
|
||||||
def _build_details(self, matrix, content):
|
|
||||||
"""Construct the docutils content for the support matrix details.
|
|
||||||
|
|
||||||
This is generated as a bullet list of features. Against each feature we
|
|
||||||
provide the description of the feature and then the details of the
|
|
||||||
hypervisor impls, with any driver specific notes that exist
|
|
||||||
"""
|
|
||||||
detailstitle = nodes.subtitle(text="Details")
|
|
||||||
details = nodes.bullet_list()
|
|
||||||
|
|
||||||
content.append(detailstitle)
|
|
||||||
content.append(details)
|
|
||||||
|
|
||||||
# One list entry for each feature we're reporting on
|
|
||||||
for feature in matrix.features:
|
|
||||||
item = nodes.list_item()
|
|
||||||
|
|
||||||
status = feature.status
|
|
||||||
if feature.group is not None:
|
|
||||||
status += "(" + feature.group + ")"
|
|
||||||
|
|
||||||
# The hypervisor target name linked from summary table
|
|
||||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
|
||||||
feature.key)
|
|
||||||
|
|
||||||
# Highlight the feature title name
|
|
||||||
item.append(nodes.strong(text=feature.title,
|
|
||||||
ids=[id]))
|
|
||||||
|
|
||||||
para = nodes.paragraph()
|
|
||||||
para.append(nodes.strong(text="Status: " + status + ". "))
|
|
||||||
if feature.notes is not None:
|
|
||||||
para.append(nodes.inline(text=feature.notes))
|
|
||||||
item.append(para)
|
|
||||||
|
|
||||||
if feature.cli:
|
|
||||||
item.append(self._create_cli_paragraph(feature))
|
|
||||||
|
|
||||||
para_divers = nodes.paragraph()
|
|
||||||
para_divers.append(nodes.strong(text="drivers:"))
|
|
||||||
# A sub-list giving details of each hypervisor target
|
|
||||||
impls = nodes.bullet_list()
|
|
||||||
for key in feature.implementations:
|
|
||||||
target = matrix.targets[key]
|
|
||||||
impl = feature.implementations[key]
|
|
||||||
subitem = nodes.list_item()
|
|
||||||
|
|
||||||
id = re.sub("[^a-zA-Z0-9_]", "_",
|
|
||||||
feature.key + "_" + key)
|
|
||||||
subitem += [
|
|
||||||
nodes.strong(text=target.title + ": "),
|
|
||||||
nodes.literal(text=impl.status,
|
|
||||||
classes=["sp_impl_" + impl.status],
|
|
||||||
ids=[id]),
|
|
||||||
]
|
|
||||||
if impl.notes is not None:
|
|
||||||
subitem.append(self._create_notes_paragraph(impl.notes))
|
|
||||||
impls.append(subitem)
|
|
||||||
|
|
||||||
para_divers.append(impls)
|
|
||||||
item.append(para_divers)
|
|
||||||
details.append(item)
|
|
||||||
|
|
||||||
def _build_notes(self, content):
|
|
||||||
"""Construct a list of notes content for the support matrix.
|
|
||||||
|
|
||||||
This is generated as a bullet list.
|
|
||||||
"""
|
|
||||||
notestitle = nodes.subtitle(text="Notes")
|
|
||||||
notes = nodes.bullet_list()
|
|
||||||
|
|
||||||
content.append(notestitle)
|
|
||||||
content.append(notes)
|
|
||||||
|
|
||||||
NOTES = [
|
|
||||||
"Virtuozzo was formerly named Parallels in this document"
|
|
||||||
]
|
|
||||||
|
|
||||||
for note in NOTES:
|
|
||||||
item = nodes.list_item()
|
|
||||||
item.append(nodes.strong(text=note))
|
|
||||||
notes.append(item)
|
|
||||||
|
|
||||||
def _create_cli_paragraph(self, feature):
|
|
||||||
"""Create a paragraph which represents the CLI commands of the feature.
|
|
||||||
|
|
||||||
The paragraph will have a bullet list of CLI commands.
|
|
||||||
"""
|
|
||||||
para = nodes.paragraph()
|
|
||||||
para.append(nodes.strong(text="CLI commands:"))
|
|
||||||
commands = nodes.bullet_list()
|
|
||||||
for c in feature.cli.split(";"):
|
|
||||||
cli_command = nodes.list_item()
|
|
||||||
cli_command += nodes.literal(text=c, classes=["sp_cli"])
|
|
||||||
commands.append(cli_command)
|
|
||||||
para.append(commands)
|
|
||||||
return para
|
|
||||||
|
|
||||||
def _create_notes_paragraph(self, notes):
|
|
||||||
"""Construct a paragraph which represents the implementation notes.
|
|
||||||
|
|
||||||
The paragraph consists of text and clickable URL nodes if links were
|
|
||||||
given in the notes.
|
|
||||||
"""
|
|
||||||
para = nodes.paragraph()
|
|
||||||
# links could start with http:// or https://
|
|
||||||
link_idxs = [m.start() for m in re.finditer('https?://', notes)]
|
|
||||||
start_idx = 0
|
|
||||||
for link_idx in link_idxs:
|
|
||||||
# assume the notes start with text (could be empty)
|
|
||||||
para.append(nodes.inline(text=notes[start_idx:link_idx]))
|
|
||||||
# create a URL node until the next text or the end of the notes
|
|
||||||
link_end_idx = notes.find(" ", link_idx)
|
|
||||||
if link_end_idx == -1:
|
|
||||||
# In case the notes end with a link without a blank
|
|
||||||
link_end_idx = len(notes)
|
|
||||||
uri = notes[link_idx:link_end_idx + 1]
|
|
||||||
para.append(nodes.reference("", uri, refuri=uri))
|
|
||||||
start_idx = link_end_idx + 1
|
|
||||||
|
|
||||||
# get all text after the last link (could be empty) or all of the
|
|
||||||
# text if no link was given
|
|
||||||
para.append(nodes.inline(text=notes[start_idx:]))
|
|
||||||
return para
|
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
|
||||||
app.add_directive('support_matrix', SupportMatrixDirective)
|
|
||||||
app.add_stylesheet('support-matrix.css')
|
|
@ -5,6 +5,7 @@ openstackdocstheme>=1.18.1 # Apache-2.0
|
|||||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||||
sphinxcontrib-apidoc>=0.2.0 # BSD
|
sphinxcontrib-apidoc>=0.2.0 # BSD
|
||||||
sphinxcontrib-seqdiag>=0.8.4 # BSD
|
sphinxcontrib-seqdiag>=0.8.4 # BSD
|
||||||
|
sphinx-feature-classification>=0.3.2 # Apache-2.0
|
||||||
reno>=2.5.0 # Apache-2.0
|
reno>=2.5.0 # Apache-2.0
|
||||||
os-api-ref>=1.4.0 # Apache-2.0
|
os-api-ref>=1.4.0 # Apache-2.0
|
||||||
python-ldap>=3.0.0 # PSF
|
python-ldap>=3.0.0 # PSF
|
||||||
|
@ -21,18 +21,18 @@
|
|||||||
# - operation: Public API operations.
|
# - operation: Public API operations.
|
||||||
# - feature: Features of the driver.
|
# - feature: Features of the driver.
|
||||||
#
|
#
|
||||||
# When considering which capabilities should be marked as mandatory, consider
|
# When considering which capabilities should be marked as required, consider
|
||||||
# the following guiding principles.
|
# the following guiding principles.
|
||||||
#
|
#
|
||||||
# The 'status' field takes possible values:
|
# The 'status' field takes possible values:
|
||||||
#
|
#
|
||||||
# - mandatory: Unconditionally required to be implemented.
|
# - required: Unconditionally required to be implemented.
|
||||||
# - optional: Optional to support, but nice to have.
|
# - optional: Optional to support, but nice to have.
|
||||||
# - choice(group): At least one of the options within the named group
|
# - choice(group): At least one of the options within the named group
|
||||||
# must be implemented.
|
# must be implemented.
|
||||||
# - conditional(cond): Required, if the referenced condition is met.
|
# - conditional(cond): Required, if the referenced condition is met.
|
||||||
#
|
#
|
||||||
# The value against each 'impl-XXXX' entry refers to the level of the
|
# The value against each 'driver.XXXX' entry refers to the level of the
|
||||||
# implementation of the feature in that driver:
|
# implementation of the feature in that driver:
|
||||||
#
|
#
|
||||||
# - complete: Fully implemented, expected to work at all times.
|
# - complete: Fully implemented, expected to work at all times.
|
||||||
@ -42,79 +42,89 @@
|
|||||||
# - missing: Not implemented at all.
|
# - missing: Not implemented at all.
|
||||||
#
|
#
|
||||||
# In the case of the driver being marked as 'partial', then
|
# In the case of the driver being marked as 'partial', then
|
||||||
# 'notes-XXX' entry should be used to explain the caveats around the
|
# 'notes' entry should be used to explain the caveats around the
|
||||||
# implementation.
|
# implementation.
|
||||||
#
|
#
|
||||||
# The 'cli' field takes a list of client commands, separated by semicolon.
|
# The 'cli' field takes a list of client commands, separated by semicolon.
|
||||||
# These CLi commands are related to that feature.
|
# These CLi commands are related to that operation.
|
||||||
# Example:
|
# Example:
|
||||||
# cli=openstack domain list;openstack domain show <domain>
|
# cli=openstack domain list;openstack domain show <domain>
|
||||||
#
|
#
|
||||||
[targets]
|
|
||||||
# List of driver implementations for which we are going to track the status of
|
# List of driver implementations for which we are going to track the status of
|
||||||
# features. This list only covers drivers that are in tree. Out of tree
|
# features. This list only covers drivers that are in tree. Out of tree
|
||||||
# drivers should maintain their own equivalent document, and merge it with this
|
# drivers should maintain their own equivalent document, and merge it with this
|
||||||
# when their code merges into core.
|
# when their code merges into core.
|
||||||
|
|
||||||
driver-impl-sql=SQL
|
[driver.sql]
|
||||||
driver-impl-ldap=LDAP
|
title=SQL
|
||||||
driver-impl-oauth1=OAuth v1.0a
|
|
||||||
driver-impl-external=REMOTE_USER
|
|
||||||
driver-impl-oidc=OpenID Connect
|
|
||||||
driver-impl-samlv2=SAML v2
|
|
||||||
|
|
||||||
[feature.local_authentication]
|
[driver.ldap]
|
||||||
|
title=LDAP
|
||||||
|
|
||||||
|
[driver.oauth1]
|
||||||
|
title=OAuth v1.0a
|
||||||
|
|
||||||
|
[driver.external]
|
||||||
|
title=REMOTE_USER
|
||||||
|
|
||||||
|
[driver.oidc]
|
||||||
|
title=OpenID Connect
|
||||||
|
|
||||||
|
[driver.samlv2]
|
||||||
|
title=SAML v2
|
||||||
|
|
||||||
|
[operation.local_authentication]
|
||||||
title=Local authentication
|
title=Local authentication
|
||||||
status=optional
|
status=optional
|
||||||
notes=Authenticate with keystone by providing credentials directly to keystone.
|
notes=Authenticate with keystone by providing credentials directly to keystone.
|
||||||
driver-impl-sql=complete
|
driver.sql=complete
|
||||||
driver-impl-ldap=complete
|
driver.ldap=complete
|
||||||
driver-impl-oauth1=complete
|
driver.oauth1=complete
|
||||||
driver-impl-external=missing
|
driver.external=missing
|
||||||
driver-impl-oidc=missing
|
driver.oidc=missing
|
||||||
driver-impl-samlv2=missing
|
driver.samlv2=missing
|
||||||
|
|
||||||
[feature.external_authentication]
|
[operation.external_authentication]
|
||||||
title=External authentication
|
title=External authentication
|
||||||
status=optional
|
status=optional
|
||||||
notes=Authenticate with keystone by providing credentials to an external system
|
notes=Authenticate with keystone by providing credentials to an external system
|
||||||
that keystone trusts (as with federation).
|
that keystone trusts (as with federation).
|
||||||
driver-impl-sql=missing
|
driver.sql=missing
|
||||||
driver-impl-ldap=missing
|
driver.ldap=missing
|
||||||
driver-impl-oauth1=missing
|
driver.oauth1=missing
|
||||||
driver-impl-external=complete
|
driver.external=complete
|
||||||
driver-impl-oidc=complete
|
driver.oidc=complete
|
||||||
driver-impl-samlv2=complete
|
driver.samlv2=complete
|
||||||
|
|
||||||
[feature.identity_crud]
|
[operation.identity_crud]
|
||||||
title=Identity management
|
title=Identity management
|
||||||
status=optional
|
status=optional
|
||||||
notes=Create, update, enable/disable, and delete users via Keystone's HTTP API.
|
notes=Create, update, enable/disable, and delete users via Keystone's HTTP API.
|
||||||
driver-impl-sql=complete
|
driver.sql=complete
|
||||||
driver-impl-ldap=partial
|
driver.ldap=partial
|
||||||
driver-impl-oauth1=complete
|
driver.oauth1=complete
|
||||||
driver-impl-external=missing
|
driver.external=missing
|
||||||
driver-impl-oidc=missing
|
driver.oidc=missing
|
||||||
driver-impl-samlv2=missing
|
driver.samlv2=missing
|
||||||
|
|
||||||
[feature.pci_controls]
|
[operation.pci_controls]
|
||||||
title=PCI-DSS controls
|
title=PCI-DSS controls
|
||||||
status=optional
|
status=optional
|
||||||
notes=Configure keystone to enforce PCI-DSS compliant security controls.
|
notes=Configure keystone to enforce PCI-DSS compliant security controls.
|
||||||
driver-impl-sql=complete
|
driver.sql=complete
|
||||||
driver-impl-ldap=partial
|
driver.ldap=partial
|
||||||
driver-impl-oauth1=missing
|
driver.oauth1=missing
|
||||||
driver-impl-external=partial
|
driver.external=partial
|
||||||
driver-impl-oidc=missing
|
driver.oidc=missing
|
||||||
driver-impl-samlv2=missing
|
driver.samlv2=missing
|
||||||
|
|
||||||
[feature.auditing]
|
[operation.auditing]
|
||||||
title=Auditing
|
title=Auditing
|
||||||
status=optional
|
status=optional
|
||||||
notes=Audit authentication flows using PyCADF.
|
notes=Audit authentication flows using PyCADF.
|
||||||
driver-impl-sql=complete
|
driver.sql=complete
|
||||||
driver-impl-ldap=complete
|
driver.ldap=complete
|
||||||
driver-impl-oauth1=missing
|
driver.oauth1=missing
|
||||||
driver-impl-external=missing
|
driver.external=missing
|
||||||
driver-impl-oidc=complete
|
driver.oidc=complete
|
||||||
driver-impl-samlv2=complete
|
driver.samlv2=complete
|
||||||
|
@ -10,53 +10,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
# This file contains a specification of what feature capabilities each driver
|
# For information about the format of this file, refer to the documentation
|
||||||
# is able to support. Feature capabilities include what API operations are
|
# for sphinx-feature-classification:
|
||||||
# supported, what backend behaviors features can be used and what aspects of
|
|
||||||
# the driver implementation can be configured. The capabilities can be
|
|
||||||
# considered to be structured into nested groups, but in this file they have
|
|
||||||
# been flattened for ease of representation. The section names represent the
|
|
||||||
# group structure. At the top level there are the following groups defined:
|
|
||||||
#
|
#
|
||||||
# - operation: Public API operations.
|
# https://docs.openstack.org/sphinx-feature-classification/latest/
|
||||||
# - feature: Features of the token format.
|
|
||||||
#
|
[driver.fernet]
|
||||||
# When considering which capabilities should be marked as mandatory, consider
|
title=Fernet tokens
|
||||||
# the following guiding principles.
|
|
||||||
#
|
[driver.jws]
|
||||||
# The 'status' field takes possible values:
|
title=JWS tokens
|
||||||
#
|
|
||||||
# - mandatory: Unconditionally required to be implemented.
|
|
||||||
# - optional: Optional to support, but nice to have.
|
|
||||||
# - choice(group): At least one of the options within the named group
|
|
||||||
# must be implemented.
|
|
||||||
# - conditional(cond): Required, if the referenced condition is met.
|
|
||||||
#
|
|
||||||
# The value against each 'driver-impl-XXXX' entry refers to the level of the
|
|
||||||
# implementation of the feature in that driver:
|
|
||||||
#
|
|
||||||
# - complete: Fully implemented, expected to work at all times.
|
|
||||||
# - partial: Implemented, but with caveats about when it will work.
|
|
||||||
# For example, some configurations or hardware or guest OS may not
|
|
||||||
# support it.
|
|
||||||
# - missing: Not implemented at all.
|
|
||||||
#
|
|
||||||
# In the case of the driver being marked as 'partial', then
|
|
||||||
# 'driver-notes-XXX' entry should be used to explain the caveats around the
|
|
||||||
# implementation.
|
|
||||||
#
|
|
||||||
# The 'cli' field takes a list of client commands, separated by semicolon.
|
|
||||||
# These CLi commands are related to that feature.
|
|
||||||
# Example:
|
|
||||||
# cli=openstack domain list;openstack domain show <domain>
|
|
||||||
#
|
|
||||||
[targets]
|
|
||||||
# List of driver implementations for which we are going to track the status of
|
|
||||||
# features. This list only covers drivers that are in tree. Out of tree
|
|
||||||
# drivers should maintain their own equivalent document, and merge it with this
|
|
||||||
# when their code merges into core.
|
|
||||||
driver-impl-fernet=Fernet tokens
|
|
||||||
driver-impl-jws=JWS tokens
|
|
||||||
|
|
||||||
[operation.create_unscoped_token]
|
[operation.create_unscoped_token]
|
||||||
title=Create unscoped token
|
title=Create unscoped token
|
||||||
@ -65,8 +28,8 @@ notes=All token providers must be capable of issuing tokens without an explicit
|
|||||||
scope of authorization.
|
scope of authorization.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> token issue
|
--os-password=<password> token issue
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.create_system_token]
|
[operation.create_system_token]
|
||||||
title=Create system-scoped token
|
title=Create system-scoped token
|
||||||
@ -74,8 +37,8 @@ status=mandatory
|
|||||||
notes=All token providers must be capable of issuing system-scoped tokens.
|
notes=All token providers must be capable of issuing system-scoped tokens.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-system-scope all token issue
|
--os-system-scope all token issue
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.create_project_scoped_token]
|
[operation.create_project_scoped_token]
|
||||||
title=Create project-scoped token
|
title=Create project-scoped token
|
||||||
@ -84,8 +47,8 @@ notes=All token providers must be capable of issuing project-scoped tokens.
|
|||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-project-name=<project>
|
--os-password=<password> --os-project-name=<project>
|
||||||
--os-project-domain-name=<domain> token issue
|
--os-project-domain-name=<domain> token issue
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.create_domain_scoped_token]
|
[operation.create_domain_scoped_token]
|
||||||
title=Create domain-scoped token
|
title=Create domain-scoped token
|
||||||
@ -94,8 +57,8 @@ notes=Domain-scoped tokens are not required for all use cases, and for some use
|
|||||||
cases, projects can be used instead.
|
cases, projects can be used instead.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-domain-name=<domain> token issue
|
--os-password=<password> --os-domain-name=<domain> token issue
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.create_trust_scoped_token]
|
[operation.create_trust_scoped_token]
|
||||||
title=Create trust-scoped token
|
title=Create trust-scoped token
|
||||||
@ -104,16 +67,16 @@ notes=Tokens scoped to a trust convey only the user impersonation and
|
|||||||
project-based authorization attributes included in the delegation.
|
project-based authorization attributes included in the delegation.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-trust-id=<trust> token issue
|
--os-password=<password> --os-trust-id=<trust> token issue
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.create_token_using_oauth]
|
[operation.create_token_using_oauth]
|
||||||
title=Create a token given an OAuth access token
|
title=Create a token given an OAuth access token
|
||||||
status=optional
|
status=optional
|
||||||
notes=OAuth access tokens can be exchanged for keystone tokens.
|
notes=OAuth access tokens can be exchanged for keystone tokens.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[operation.revoke_token]
|
[operation.revoke_token]
|
||||||
title=Revoke a token
|
title=Revoke a token
|
||||||
@ -123,8 +86,8 @@ notes=Tokens may be individually revoked, such as when a user logs out of
|
|||||||
single token may be revoked as a result of this operation (such as when the
|
single token may be revoked as a result of this operation (such as when the
|
||||||
revoked token was previously used to create additional tokens).
|
revoked token was previously used to create additional tokens).
|
||||||
cli=openstack token revoke
|
cli=openstack token revoke
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[feature.online_validation]
|
[feature.online_validation]
|
||||||
title=Online validation
|
title=Online validation
|
||||||
@ -132,8 +95,8 @@ status=mandatory
|
|||||||
notes=Keystone must be able to validate the tokens that it issues when
|
notes=Keystone must be able to validate the tokens that it issues when
|
||||||
presented with a token that it previously issued.
|
presented with a token that it previously issued.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
|
||||||
[feature.offline_validation]
|
[feature.offline_validation]
|
||||||
title=Offline validation
|
title=Offline validation
|
||||||
@ -142,8 +105,8 @@ notes=Services using Keystone for authentication may want to validate tokens
|
|||||||
themselves, rather than calling back to keystone, in order to improve
|
themselves, rather than calling back to keystone, in order to improve
|
||||||
performance and scalability.
|
performance and scalability.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-fernet=missing
|
driver.fernet=missing
|
||||||
driver-impl-jws=missing
|
driver.jws=missing
|
||||||
|
|
||||||
[feature.non_persistent]
|
[feature.non_persistent]
|
||||||
title=Non-persistent
|
title=Non-persistent
|
||||||
@ -153,5 +116,5 @@ notes=If a token format does not require persistence (such as to a SQL
|
|||||||
keystone can issue at once, and there is no need to perform clean up
|
keystone can issue at once, and there is no need to perform clean up
|
||||||
operations such as `keystone-manage token_flush`.
|
operations such as `keystone-manage token_flush`.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-fernet=complete
|
driver.fernet=complete
|
||||||
driver-impl-jws=complete
|
driver.jws=complete
|
||||||
|
@ -24,19 +24,6 @@
|
|||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# NOTE(dstanek): adds _ to the builtins so keystone modules can be imported
|
|
||||||
__builtins__['_'] = str
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
sys.path.insert(0, os.path.abspath('../../'))
|
|
||||||
sys.path.insert(0, os.path.abspath('../'))
|
|
||||||
sys.path.insert(0, os.path.abspath('./'))
|
|
||||||
|
|
||||||
# -- General configuration ----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
@ -52,9 +39,9 @@ extensions = ['sphinx.ext.coverage',
|
|||||||
'oslo_policy.sphinxpolicygen',
|
'oslo_policy.sphinxpolicygen',
|
||||||
'openstackdocstheme',
|
'openstackdocstheme',
|
||||||
'oslo_policy.sphinxext',
|
'oslo_policy.sphinxext',
|
||||||
'ext.support_matrix',
|
|
||||||
'sphinxcontrib.apidoc',
|
'sphinxcontrib.apidoc',
|
||||||
'sphinxcontrib.seqdiag',
|
'sphinxcontrib.seqdiag',
|
||||||
|
'sphinx_feature_classification.support_matrix',
|
||||||
]
|
]
|
||||||
|
|
||||||
# sphinxcontrib.apidoc options
|
# sphinxcontrib.apidoc options
|
||||||
|
Loading…
Reference in New Issue
Block a user