From 752ce5a491fbe8e087e2334a7d8abe54aef33f66 Mon Sep 17 00:00:00 2001 From: Tony Breeds Date: Mon, 25 Feb 2019 12:44:16 +1100 Subject: [PATCH] Generate the constraints redirections from the deliverable data Instead of statically listing the redirections move to a dynamic model. We move the existing _extras/.htaccess to _templates/htaccess so we have some control and safety of what goes in there. Connect 'build-finished' from _exts.deliverables.py to trigger generating the redirects. Doing so here avoids re-reading the data as deliverables.py ahas already done that for us. Change-Id: If6bd59fd478593a84ebcedc3a50af3720d620d3c --- doc/source/_extra/.htaccess | 13 -- doc/source/_exts/deliverables.py | 19 ++ doc/source/_templates/htaccess | 6 + doc/source/conf.py | 4 +- doc/test/redirect-tests.txt | 1 - openstack_releases/_redirections.py | 44 +++++ openstack_releases/tests/test_redirections.py | 177 ++++++++++++++++++ tox.ini | 2 +- 8 files changed, 248 insertions(+), 18 deletions(-) delete mode 100644 doc/source/_extra/.htaccess create mode 100644 doc/source/_templates/htaccess create mode 100644 openstack_releases/_redirections.py create mode 100644 openstack_releases/tests/test_redirections.py diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess deleted file mode 100644 index 7b805336ea..0000000000 --- a/doc/source/_extra/.htaccess +++ /dev/null @@ -1,13 +0,0 @@ -redirect 301 /teams/shade.html /teams/openstacksdk.html -redirect 301 /constraints/upper/master http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master -redirect 301 /constraints/upper/train http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master -redirect 301 /constraints/upper/stein http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master -redirect 301 /constraints/upper/rocky http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky -redirect 301 /constraints/upper/queens http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens -redirect 301 /constraints/upper/pike http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/pike -redirect 301 /constraints/upper/ocata http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ocata -redirect 301 /constraints/upper/newton http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/newton -redirect 301 /constraints/upper/mitaka http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=mitaka-eol -redirect 301 /constraints/upper/liberty http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=liberty-eol -redirect 301 /constraints/upper/kilo http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=kilo-eol -redirect 301 /constraints/upper/juno http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=juno-eol diff --git a/doc/source/_exts/deliverables.py b/doc/source/_exts/deliverables.py index b607464044..a7b830aff5 100644 --- a/doc/source/_exts/deliverables.py +++ b/doc/source/_exts/deliverables.py @@ -28,6 +28,7 @@ from sphinx.util.nodes import nested_parse_with_titles from openstack_releases import deliverable from openstack_releases import links from openstack_releases import series_status +from openstack_releases._redirections import generate_constraints_redirections LOG = logging.getLogger(__name__) @@ -467,6 +468,23 @@ class HighlightsDirective(rst.Directive): return node.children +def build_finished(app, exception): + if exception is not None: + return + + rendered_output = app.builder.templates.render( + 'htaccess', + dict(redirections=generate_constraints_redirections(_deliverables)) + ) + output_full_name = os.path.join(app.builder.outdir, '.htaccess') + with open(output_full_name, "w") as f: + f.write(rendered_output) + LOG.info('Wrote Redirections to %s' % (output_full_name)) + # NOTE(tonyb): We want to output this here because we won't be able to + # get to it via http{,s} so this helps debugging. + LOG.debug(rendered_output) + + def setup(app): _initialize_deliverable_data() app.add_directive('deliverable', DeliverableDirective) @@ -474,4 +492,5 @@ def setup(app): IndependentDeliverablesDirective) app.add_directive('team', TeamDirective) app.add_directive('serieshighlights', HighlightsDirective) + app.connect('build-finished', build_finished) _generate_team_pages() diff --git a/doc/source/_templates/htaccess b/doc/source/_templates/htaccess new file mode 100644 index 0000000000..573ce5ef2e --- /dev/null +++ b/doc/source/_templates/htaccess @@ -0,0 +1,6 @@ +redirect 301 /teams/shade.html /teams/openstacksdk.html +redirect 301 /constraints/upper/master http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master +{%- for redirection in redirections %} +redirect {{ redirection.code }} /constraints/upper/{{ redirection.src }} http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h={{ redirection.dst }} +{%- endfor %} + diff --git a/doc/source/conf.py b/doc/source/conf.py index 5ab5c1fa16..e29b1798d7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -30,7 +30,7 @@ config_generator_config_file = 'config-generator.conf' # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. -# templates_path = [] +templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' @@ -66,8 +66,6 @@ htmlhelp_basename = '%sdoc' % project git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1" html_last_updated_fmt = os.popen(git_cmd).read() -html_extra_path = ['_extra'] - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt index 15040e7b3c..38822ec821 100644 --- a/doc/test/redirect-tests.txt +++ b/doc/test/redirect-tests.txt @@ -1,6 +1,5 @@ /teams/shade.html 301 /teams/openstacksdk.html /constraints/upper/master 301 http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master -/constraints/upper/train 301 http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master /constraints/upper/stein 301 http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master /constraints/upper/rocky 301 http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/rocky /constraints/upper/queens 301 http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens diff --git a/openstack_releases/_redirections.py b/openstack_releases/_redirections.py new file mode 100644 index 0000000000..8a625ffe0d --- /dev/null +++ b/openstack_releases/_redirections.py @@ -0,0 +1,44 @@ +# All Rights Reserved. +# +# 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. + +from sphinx.util import logging + + +LOG = logging.getLogger(__name__) + + +def generate_constraints_redirections(_deliverables): + redirections = [] + # Loop through all the releases for requirements + for deliv in _deliverables.get_deliverable_history('requirements'): + # Any open deliverables should point to master + target = 'master' + + # Unless there is a specific stable branch + for branch in deliv.branches: + if branch.name == 'stable/%s' % (deliv.series): + target = branch.name + break + + # Or we have a ${series}-eol tag + for release in deliv.releases: + if release.is_eol: + target = str(release.version) + break + + # Insert into the begining of the list so that redirections are + # master -> juno + redirections.insert(0, dict(code=301, src=deliv.series, dst=target)) + + return redirections diff --git a/openstack_releases/tests/test_redirections.py b/openstack_releases/tests/test_redirections.py new file mode 100644 index 0000000000..8a4613a149 --- /dev/null +++ b/openstack_releases/tests/test_redirections.py @@ -0,0 +1,177 @@ +# All Rights Reserved. +# +# 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 textwrap + +from oslotest import base + +from openstack_releases._redirections import generate_constraints_redirections +from openstack_releases import deliverable +from openstack_releases import yamlutils + + +# Create a Fake Deliverables class we really only need an object with +# get_deliverable_history that returns an iterable. Using the main real class +# seems like overkill and would subject us to testing chnages as the actual +# deliverables change over time. +class FakeDeliverables(object): + def __init__(self, deliverables=[]): + self._deliverables = deliverables + + def get_deliverable_history(self, name): + return self._deliverables + + +class TestRedirections(base.BaseTestCase): + # Deliverable that looks like an open development series with no branches + # or releases. + OPEN_DEVELOPMENT = deliverable.Deliverable( + team='requirements', + series='stein', + name='requirements', + data={} + ) + # Deliverable that looks like an open development series with no branches + # but has a single release + DEVELOPMENT_RELEASE = deliverable.Deliverable( + team='requirements', + series='stein', + name='requirements', + data=yamlutils.loads(textwrap.dedent(''' + releases: + - projects: + - hash: not_used + repo: openstack/requirements + version: 1.0.0 + ''')) + ) + # Deliverable that looks like an open stable series with no releases + OPEN_STABLE = deliverable.Deliverable( + team='requirements', + series='rocky', + name='requirements', + data=yamlutils.loads(textwrap.dedent(''' + branches: + - name: stable/rocky + location: + openstack/requirements: not_used + ''')) + ) + # Deliverable that looks like an open stable series with no releases but + # also has an open 'feature' branch + OPEN_UNSTABLE = deliverable.Deliverable( + team='requirements', + series='rocky', + name='requirements', + data=yamlutils.loads(textwrap.dedent(''' + branches: + - name: unstable/rocky + location: + openstack/requirements: not_used + - name: stable/rocky + location: + openstack/requirements: not_used + ''')) + ) + # Deliverable that looks like an open stable series with a release + STABLE_RELEASE = deliverable.Deliverable( + team='requirements', + series='rocky', + name='requirements', + data=yamlutils.loads(textwrap.dedent(''' + branches: + - name: stable/rocky + location: + openstack/requirements: not_used + releases: + - projects: + - hash: not_used + repo: openstack/requirements + version: 1.0.0 + ''')) + ) + # Deliverable that looks like a closed stable series + STABLE_EOL = deliverable.Deliverable( + team='requirements', + series='mitaka', + name='requirements', + data=yamlutils.loads(textwrap.dedent(''' + releases: + - projects: + - hash: not_used + repo: openstack/requirements + version: mitaka-eol + ''')) + ) + + def setUp(self): + super().setUp() + + def test_open_development(self): + deliverables = FakeDeliverables([ + self.OPEN_DEVELOPMENT, + ]) + self.assertEqual([dict(code=301, src='stein', dst='master')], + generate_constraints_redirections(deliverables)) + + def test_development_release(self): + deliverables = FakeDeliverables([ + self.DEVELOPMENT_RELEASE, + ]) + self.assertEqual([dict(code=301, src='stein', dst='master')], + generate_constraints_redirections(deliverables)) + + def test_open_stable(self): + deliverables = FakeDeliverables([ + self.OPEN_STABLE, + ]) + self.assertEqual([dict(code=301, src='rocky', dst='stable/rocky')], + generate_constraints_redirections(deliverables)) + + def test_open_unstable(self): + deliverables = FakeDeliverables([ + self.OPEN_UNSTABLE, + ]) + self.assertEqual([dict(code=301, src='rocky', dst='stable/rocky')], + generate_constraints_redirections(deliverables)) + + def test_stable_release(self): + deliverables = FakeDeliverables([ + self.STABLE_RELEASE, + ]) + self.assertEqual([dict(code=301, src='rocky', dst='stable/rocky')], + generate_constraints_redirections(deliverables)) + + def test_stable_eol(self): + deliverables = FakeDeliverables([ + self.STABLE_EOL, + ]) + self.assertEqual([dict(code=301, src='mitaka', dst='mitaka-eol')], + generate_constraints_redirections(deliverables)) + + def test_all(self): + deliverables = FakeDeliverables([ + self.STABLE_EOL, + self.STABLE_RELEASE, + self.DEVELOPMENT_RELEASE, + ]) + self.assertEqual([dict(code=301, src='stein', dst='master'), + dict(code=301, src='rocky', dst='stable/rocky'), + dict(code=301, src='mitaka', dst='mitaka-eol')], + generate_constraints_redirections(deliverables)) + + def test_empty(self): + deliverables = FakeDeliverables([]) + self.assertEqual([], + generate_constraints_redirections(deliverables)) diff --git a/tox.ini b/tox.ini index 968c8655c3..4541b61f6f 100644 --- a/tox.ini +++ b/tox.ini @@ -74,7 +74,7 @@ deps = -r{toxinidir}/doc/requirements.txt commands = sphinx-build -v -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html - whereto {toxinidir}/doc/source/_extra/.htaccess {toxinidir}/doc/test/redirect-tests.txt + whereto {toxinidir}/doc/build/html/.htaccess {toxinidir}/doc/test/redirect-tests.txt [flake8] # E123, E125 skipped as they are invalid PEP-8.