20a0a48d74
Change-Id: Id99503e561ce112c0e4803ca1566be3186c1bd31
427 lines
15 KiB
Python
427 lines
15 KiB
Python
#
|
|
# 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.
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from functools import cmp_to_key
|
|
import pydoc
|
|
|
|
from docutils import core
|
|
from docutils import nodes
|
|
import six
|
|
from sphinx.util import compat
|
|
|
|
from heat.common.i18n import _
|
|
from heat.engine import attributes
|
|
from heat.engine import plugin_manager
|
|
from heat.engine import properties
|
|
from heat.engine import support
|
|
|
|
_CODE_NAMES = {'2013.1': 'Grizzly',
|
|
'2013.2': 'Havana',
|
|
'2014.1': 'Icehouse',
|
|
'2014.2': 'Juno',
|
|
'2015.1': 'Kilo',
|
|
'5.0.0': 'Liberty',
|
|
'6.0.0': 'Mitaka',
|
|
'7.0.0': 'Newton',
|
|
'8.0.0': 'Ocata',
|
|
'9.0.0': 'Pike',
|
|
'10.0.0': 'Queens'}
|
|
|
|
all_resources = {}
|
|
|
|
|
|
class integratedrespages(nodes.General, nodes.Element):
|
|
pass
|
|
|
|
|
|
class unsupportedrespages(nodes.General, nodes.Element):
|
|
pass
|
|
|
|
|
|
class contribresourcepages(nodes.General, nodes.Element):
|
|
pass
|
|
|
|
|
|
class ResourcePages(compat.Directive):
|
|
has_content = False
|
|
required_arguments = 0
|
|
optional_arguments = 1
|
|
final_argument_whitespace = False
|
|
option_spec = {}
|
|
|
|
def path(self):
|
|
return None
|
|
|
|
def statuses(self):
|
|
return support.SUPPORT_STATUSES
|
|
|
|
def run(self):
|
|
prefix = self.arguments and self.arguments.pop() or None
|
|
content = []
|
|
for resource_type, resource_classes in _filter_resources(
|
|
prefix, self.path(), self.statuses()):
|
|
for resource_class in resource_classes:
|
|
self.resource_type = resource_type
|
|
self.resource_class = resource_class
|
|
section = self._section(content, resource_type, '%s')
|
|
|
|
self.props_schemata = properties.schemata(
|
|
self.resource_class.properties_schema)
|
|
self.attrs_schemata = attributes.schemata(
|
|
self.resource_class.attributes_schema)
|
|
# NOTE(prazumovsky): Adding base_attributes_schema dict to
|
|
# Resource class should means adding new attributes from this
|
|
# dict to documentation of each resource, else there is no
|
|
# chance to learn about base attributes.
|
|
self.attrs_schemata.update(
|
|
self.resource_class.base_attributes_schema)
|
|
self.update_policy_schemata = properties.schemata(
|
|
self.resource_class.update_policy_schema)
|
|
|
|
self._status_str(resource_class.support_status, section)
|
|
|
|
cls_doc = pydoc.getdoc(resource_class)
|
|
if cls_doc:
|
|
# allow for rst in the class comments
|
|
cls_nodes = core.publish_doctree(cls_doc).children
|
|
section.extend(cls_nodes)
|
|
|
|
self.contribute_properties(section)
|
|
self.contribute_attributes(section)
|
|
self.contribute_update_policy(section)
|
|
|
|
self.contribute_hot_syntax(section)
|
|
|
|
return content
|
|
|
|
def _version_str(self, version):
|
|
if version in _CODE_NAMES:
|
|
return _("%(version)s (%(code)s)") % {'version': version,
|
|
'code': _CODE_NAMES[version]}
|
|
else:
|
|
return version
|
|
|
|
def _status_str(self, support_status, section):
|
|
while support_status is not None:
|
|
sstatus = support_status.to_dict()
|
|
if sstatus['status'] is support.SUPPORTED:
|
|
msg = _('Available')
|
|
else:
|
|
msg = sstatus['status']
|
|
if sstatus['version'] is not None:
|
|
msg = _('%(msg)s since %(version)s') % {
|
|
'msg': msg,
|
|
'version': self._version_str(sstatus['version'])}
|
|
if sstatus['message'] is not None:
|
|
msg = _('%(msg)s - %(status_msg)s') % {
|
|
'msg': msg,
|
|
'status_msg': sstatus['message']}
|
|
if not (sstatus['status'] == support.SUPPORTED and
|
|
sstatus['version'] is None):
|
|
para = nodes.paragraph('', msg)
|
|
note = nodes.note('', para)
|
|
section.append(note)
|
|
support_status = support_status.previous_status
|
|
|
|
def _section(self, parent, title, id_pattern):
|
|
id = id_pattern % self.resource_type
|
|
section = nodes.section(ids=[id])
|
|
parent.append(section)
|
|
title = nodes.title('', title)
|
|
section.append(title)
|
|
return section
|
|
|
|
def _prop_syntax_example(self, prop):
|
|
if not prop:
|
|
return 'Value'
|
|
if prop.type == properties.Schema.LIST:
|
|
|
|
def schema(i):
|
|
return prop.schema[i] if prop.schema else None
|
|
|
|
sub_type = [self._prop_syntax_example(schema(i))
|
|
for i in range(2)]
|
|
return '[%s, %s, ...]' % tuple(sub_type)
|
|
elif prop.type == properties.Schema.MAP:
|
|
def sub_props():
|
|
for sub_key, sub_value in prop.schema.items():
|
|
if sub_value.implemented:
|
|
yield '"%s": %s' % (
|
|
sub_key, self._prop_syntax_example(sub_value))
|
|
return '{%s}' % (', '.join(sub_props()) if prop.schema else '...')
|
|
else:
|
|
return prop.type
|
|
|
|
def contribute_hot_syntax(self, parent):
|
|
section = self._section(parent, _('HOT Syntax'), '%s-hot')
|
|
props = []
|
|
for prop_key in sorted(self.props_schemata.keys()):
|
|
prop = self.props_schemata[prop_key]
|
|
if (prop.implemented
|
|
and prop.support_status.status == support.SUPPORTED):
|
|
props.append('%s: %s' % (prop_key,
|
|
self._prop_syntax_example(prop)))
|
|
|
|
props_str = ''
|
|
if props:
|
|
props_str = '''\n properties:
|
|
%s''' % ('\n '.join(props))
|
|
|
|
template = '''heat_template_version: 2015-04-30
|
|
...
|
|
resources:
|
|
...
|
|
the_resource:
|
|
type: %s%s''' % (self.resource_type, props_str)
|
|
|
|
block = nodes.literal_block(template, template, language="yaml")
|
|
section.append(block)
|
|
|
|
@staticmethod
|
|
def cmp_prop(x, y):
|
|
x_key, x_prop = x
|
|
y_key, y_prop = y
|
|
if x_prop.support_status.status == y_prop.support_status.status:
|
|
return (x_key > y_key) - (x_key < y_key)
|
|
x_status = x_prop.support_status.status
|
|
y_status = y_prop.support_status.status
|
|
if x_status == support.SUPPORTED:
|
|
return -1
|
|
if x_status == support.DEPRECATED:
|
|
return 1
|
|
return (x_status > y_status) - (x_status < y_status)
|
|
|
|
def contribute_property(self, parent, prop_key, prop, upd_para=None,
|
|
id_pattern_prefix=None):
|
|
if not id_pattern_prefix:
|
|
id_pattern_prefix = '%s-prop'
|
|
id_pattern = id_pattern_prefix + '-' + prop_key
|
|
|
|
definition = self._section(parent, prop_key, id_pattern)
|
|
|
|
self._status_str(prop.support_status, definition)
|
|
|
|
if not prop.implemented:
|
|
para = nodes.paragraph('', _('Not implemented.'))
|
|
note = nodes.note('', para)
|
|
definition.append(note)
|
|
return
|
|
|
|
if prop.description:
|
|
para = nodes.paragraph('', prop.description)
|
|
definition.append(para)
|
|
|
|
type = nodes.paragraph('', _('%s value expected.') % prop.type)
|
|
definition.append(type)
|
|
|
|
if upd_para is not None:
|
|
definition.append(upd_para)
|
|
else:
|
|
if prop.update_allowed:
|
|
upd_para = nodes.paragraph(
|
|
'', _('Can be updated without replacement.'))
|
|
definition.append(upd_para)
|
|
elif prop.immutable:
|
|
upd_para = nodes.paragraph('', _('Updates are not supported. '
|
|
'Resource update will fail on'
|
|
' any attempt to update this '
|
|
'property.'))
|
|
definition.append(upd_para)
|
|
else:
|
|
upd_para = nodes.paragraph('', _('Updates cause replacement.'))
|
|
definition.append(upd_para)
|
|
|
|
if prop.default is not None:
|
|
para = nodes.paragraph('', _('Defaults to "%s".') % prop.default)
|
|
definition.append(para)
|
|
|
|
for constraint in prop.constraints:
|
|
para = nodes.paragraph('', str(constraint))
|
|
definition.append(para)
|
|
|
|
sub_schema = None
|
|
if prop.schema and prop.type == properties.Schema.MAP:
|
|
para = nodes.paragraph()
|
|
emph = nodes.emphasis('', _('Map properties:'))
|
|
para.append(emph)
|
|
definition.append(para)
|
|
sub_schema = prop.schema
|
|
|
|
elif prop.schema and prop.type == properties.Schema.LIST:
|
|
para = nodes.paragraph()
|
|
emph = nodes.emphasis('', _('List contents:'))
|
|
para.append(emph)
|
|
definition.append(para)
|
|
sub_schema = prop.schema
|
|
|
|
if sub_schema:
|
|
for _key, _prop in sorted(sub_schema.items(),
|
|
key=cmp_to_key(self.cmp_prop)):
|
|
if _prop.support_status.status != support.HIDDEN:
|
|
indent = nodes.block_quote()
|
|
definition.append(indent)
|
|
self.contribute_property(
|
|
indent, _key, _prop, upd_para, id_pattern)
|
|
|
|
def contribute_properties(self, parent):
|
|
if not self.props_schemata:
|
|
return
|
|
|
|
props = dict((k, v) for k, v in self.props_schemata.items()
|
|
if v.support_status.status != support.HIDDEN)
|
|
|
|
required_props = dict((k, v) for k, v in props.items()
|
|
if v.required)
|
|
if required_props:
|
|
section = self._section(
|
|
parent, _('Required Properties'), '%s-props-req')
|
|
|
|
for prop_key, prop in sorted(required_props.items(),
|
|
key=cmp_to_key(self.cmp_prop)):
|
|
self.contribute_property(section, prop_key, prop)
|
|
|
|
optional_props = dict((k, v) for k, v in props.items()
|
|
if not v.required)
|
|
if optional_props:
|
|
section = self._section(
|
|
parent, _('Optional Properties'), '%s-props-opt')
|
|
|
|
for prop_key, prop in sorted(optional_props.items(),
|
|
key=cmp_to_key(self.cmp_prop)):
|
|
self.contribute_property(section, prop_key, prop)
|
|
|
|
def contribute_attributes(self, parent):
|
|
if not self.attrs_schemata:
|
|
return
|
|
section = self._section(parent, _('Attributes'), '%s-attrs')
|
|
for prop_key, prop in sorted(self.attrs_schemata.items()):
|
|
if prop.support_status.status != support.HIDDEN:
|
|
description = prop.description
|
|
attr_section = self._section(
|
|
section, prop_key, '%s-attr-' + prop_key)
|
|
|
|
self._status_str(prop.support_status, attr_section)
|
|
|
|
if description:
|
|
def_para = nodes.paragraph('', description)
|
|
attr_section.append(def_para)
|
|
|
|
def contribute_update_policy(self, parent):
|
|
if not self.update_policy_schemata:
|
|
return
|
|
section = self._section(parent, _('update_policy'), '%s-updpolicy')
|
|
for _key, _prop in sorted(self.update_policy_schemata.items(),
|
|
key=cmp_to_key(self.cmp_prop)):
|
|
self.contribute_property(section, _key, _prop)
|
|
|
|
|
|
class IntegrateResourcePages(ResourcePages):
|
|
|
|
def path(self):
|
|
return 'heat.engine.resources'
|
|
|
|
def statuses(self):
|
|
return [support.SUPPORTED]
|
|
|
|
|
|
class UnsupportedResourcePages(ResourcePages):
|
|
|
|
def path(self):
|
|
return 'heat.engine.resources'
|
|
|
|
def statuses(self):
|
|
return [s for s in support.SUPPORT_STATUSES if s != support.SUPPORTED]
|
|
|
|
|
|
class ContribResourcePages(ResourcePages):
|
|
|
|
def path(self):
|
|
return 'heat.engine.plugins'
|
|
|
|
|
|
def _filter_resources(prefix=None, path=None, statuses=None):
|
|
|
|
def not_hidden_match(cls):
|
|
return cls.support_status.status != support.HIDDEN
|
|
|
|
def prefix_match(name):
|
|
return prefix is None or name.startswith(prefix)
|
|
|
|
def path_match(cls):
|
|
return path is None or cls.__module__.startswith(path)
|
|
|
|
def status_match(cls):
|
|
return cls.support_status.status in statuses
|
|
|
|
statuses = statuses or []
|
|
filtered_resources = {}
|
|
for name in sorted(all_resources.keys()):
|
|
if prefix_match(name):
|
|
for cls in all_resources.get(name):
|
|
if (path_match(cls) and status_match(cls) and
|
|
not_hidden_match(cls)):
|
|
if filtered_resources.get(name) is not None:
|
|
filtered_resources[name].append(cls)
|
|
else:
|
|
filtered_resources[name] = [cls]
|
|
|
|
return sorted(six.iteritems(filtered_resources))
|
|
|
|
|
|
def _load_all_resources():
|
|
manager = plugin_manager.PluginManager('heat.engine.resources')
|
|
resource_mapping = plugin_manager.PluginMapping('resource')
|
|
res_plugin_mappings = resource_mapping.load_all(manager)
|
|
|
|
for mapping in res_plugin_mappings:
|
|
name, cls = mapping
|
|
if all_resources.get(name) is not None:
|
|
all_resources[name].append(cls)
|
|
else:
|
|
all_resources[name] = [cls]
|
|
|
|
|
|
def link_resource(app, env, node, contnode):
|
|
reftarget = node.attributes['reftarget']
|
|
for resource_name in all_resources:
|
|
if resource_name.lower() == reftarget.lower():
|
|
refnode = nodes.reference('', '', internal=True)
|
|
refnode['reftitle'] = resource_name
|
|
if resource_name.startswith('AWS'):
|
|
source = 'template_guide/cfn'
|
|
else:
|
|
source = 'template_guide/openstack'
|
|
uri = app.builder.get_relative_uri(
|
|
node.attributes['refdoc'], source)
|
|
refnode['refuri'] = '%s#%s' % (uri, resource_name)
|
|
refnode.append(contnode)
|
|
return refnode
|
|
|
|
|
|
def setup(app):
|
|
_load_all_resources()
|
|
app.add_node(integratedrespages)
|
|
|
|
app.add_directive('integratedrespages', IntegrateResourcePages)
|
|
|
|
app.add_node(unsupportedrespages)
|
|
|
|
app.add_directive('unsupportedrespages', UnsupportedResourcePages)
|
|
|
|
app.add_node(contribresourcepages)
|
|
|
|
app.add_directive('contribrespages', ContribResourcePages)
|
|
|
|
app.connect('missing-reference', link_resource)
|