From 8a63c624ba30f9d83a702f2176b3dda506ef7ebc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 18 Sep 2025 13:55:47 +0100 Subject: [PATCH 1/9] Remove lower-constraint management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We stopped doing this nearly 3½ years ago [1]. We no longer use this tooling in any of our jobs either. It is therefore time to delete all this code. [1] https://governance.openstack.org/tc/resolutions/20220414-drop-lower-constraints Change-Id: I64ba0cec3eeac3f7ded1354bd768f7022d863dc4 Signed-off-by: Stephen Finucane --- openstack_requirements/check.py | 128 --------- openstack_requirements/project.py | 4 - openstack_requirements/tests/test_check.py | 250 ------------------ openstack_requirements/tests/test_project.py | 4 +- .../files/project-requirements-change.py | 9 - 5 files changed, 2 insertions(+), 393 deletions(-) diff --git a/openstack_requirements/check.py b/openstack_requirements/check.py index 872301a36..e14547485 100644 --- a/openstack_requirements/check.py +++ b/openstack_requirements/check.py @@ -18,7 +18,6 @@ import re from packaging import markers -from packaging import specifiers from openstack_requirements import project from openstack_requirements import requirement @@ -303,130 +302,3 @@ def validate( ) return failed - - -def _find_constraint(req, constraints): - """Return the constraint matching the markers for req. - - Given a requirement, find the constraint with matching markers. - If none match, find a constraint without any markers at all. - Otherwise return None. - """ - if req.markers: - req_markers = markers.Marker(req.markers) - for constraint_setting, _ in constraints: - if constraint_setting.markers == req.markers: - return constraint_setting - if not constraint_setting.markers: - # There is no point in performing the complex - # comparison for a constraint that has no markers, so - # we skip it here. If we find no closer match then the - # loop at the end of the function will look for a - # constraint without a marker and use that. - continue - # NOTE(dhellmann): This is a very naive attempt to check - # marker compatibility that relies on internal - # implementation details of the packaging library. The - # best way to ensure the constraint and requirements match - # is to use the same marker string in the corresponding - # lines. - c_markers = markers.Marker(constraint_setting.markers) - env = { - str(var): str(val) - for var, op, val in c_markers._markers # WARNING: internals - } - if req_markers.evaluate(env): - return constraint_setting - # Try looking for a constraint without any markers. - for constraint_setting, _ in constraints: - if not constraint_setting.markers: - return constraint_setting - return None - - -def validate_lower_constraints(req_list, constraints, denylist): - """Return True if there is an error. - - :param reqs: RequirementsList for the head of the branch - :param constraints: Parsed lower-constraints.txt or None - - """ - if constraints is None: - return False - - parsed_constraints = requirement.parse(constraints) - - failed = False - - for fname, freqs in req_list.reqs_by_file.items(): - - if fname == 'doc/requirements.txt': - # Skip things that are not needed for unit or functional - # tests. - continue - - print("Validating lower constraints of {}".format(fname)) - - for name, reqs in freqs.items(): - - if name in denylist: - continue - - if name not in parsed_constraints: - print('ERROR: Package {!r} is used in {} ' - 'but not in lower-constraints.txt'.format( - name, fname)) - failed = True - continue - - for req in reqs: - spec = specifiers.SpecifierSet(req.specifiers) - # FIXME(dhellmann): This will only find constraints - # where the markers match the requirements list - # exactly, so we can't do things like use different - # constrained versions for different versions of - # python 3 if the requirement range is expressed as - # python_version>3.0. We can support different - # versions if there is a different requirement - # specification for each version of python. I don't - # really know how smart we want this to be, because - # I'm not sure we want to support extremely - # complicated dependency sets. - constraint_setting = _find_constraint( - req, - parsed_constraints[name], - ) - if not constraint_setting: - print('ERROR: Unable to find constraint for {} ' - 'matching {!r} or without any markers.'.format( - name, req.markers)) - failed = True - continue - - version = constraint_setting.specifiers.lstrip('=') - - if not spec.contains(version): - print('ERROR: Package {!r} is constrained to {} ' - 'which is incompatible with the settings {} ' - 'from {}.'.format( - name, version, req, fname)) - failed = True - - min = [ - s - for s in req.specifiers.split(',') - if '>' in s - ] - if not min: - # No minimum specified. Ignore this and let some - # other validation trap the error. - continue - - expected = min[0].lstrip('>=') - if version != expected: - print('ERROR: Package {!r} is constrained to {} ' - 'which does not match ' - 'the minimum version specifier {} in {}'.format( - name, version, expected, fname)) - failed = True - return failed diff --git a/openstack_requirements/project.py b/openstack_requirements/project.py index 8fdaec34b..02ec716b9 100644 --- a/openstack_requirements/project.py +++ b/openstack_requirements/project.py @@ -71,8 +71,4 @@ def read(root): target_files.append('test-requirements-py%s.txt' % py_version) for target_file in target_files: _safe_read(result, target_file, output=requirements) - # Read lower-constraints.txt and ensure the key is always present - # in case the file is missing. - result['lower-constraints.txt'] = None - _safe_read(result, 'lower-constraints.txt') return result diff --git a/openstack_requirements/tests/test_check.py b/openstack_requirements/tests/test_check.py index 43ce81dc8..8983e5f62 100644 --- a/openstack_requirements/tests/test_check.py +++ b/openstack_requirements/tests/test_check.py @@ -541,256 +541,6 @@ def test_new_item_matches_py3_allowed_no_py2(self): ) -class TestValidateLowerConstraints(testtools.TestCase): - - def setUp(self): - super(TestValidateLowerConstraints, self).setUp() - self._stdout_fixture = fixtures.StringStream('stdout') - self.stdout = self.useFixture(self._stdout_fixture).stream - self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout)) - - def test_no_constraints_file(self): - constraints_content = None - project_data = { - 'requirements': {'requirements.txt': 'name>=1.2,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_no_min(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_matches(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.2,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_not_constrained(self): - constraints_content = textwrap.dedent(""" - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.2,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertTrue( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_mismatch_denylisted(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.3,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse('name'), - ) - ) - - def test_lower_bound_lower(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.1,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertTrue( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_lower_bound_higher(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.3,!=1.4'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertTrue( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_constrained_version_excluded(self): - constraints_content = textwrap.dedent(""" - name==1.2 - """) - project_data = { - 'requirements': {'requirements.txt': 'name>=1.1,!=1.2'}, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertTrue( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_constraints_with_markers(self): - constraints_content = textwrap.dedent(""" - name==1.1;python_version=='2.7' - name==2.0;python_version=='3.5' - name==2.0;python_version=='3.6' - """) - project_data = { - 'requirements': { - 'requirements.txt': textwrap.dedent(""" - name>=1.1,!=1.2;python_version=='2.7' - name>=2.0;python_version=='3.5' - name>=2.0;python_version=='3.6' - """), - }, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_constraints_with_markers_missing_one_req(self): - constraints_content = textwrap.dedent(""" - name==1.1;python_version=='2.7' - name==2.0;python_version=='3.5' - name==2.0;python_version=='3.6' - """) - project_data = { - 'requirements': { - 'requirements.txt': textwrap.dedent(""" - name>=1.1,!=1.2;python_version=='2.7' - name>=2.0;python_version=='3.5' - """), - }, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_constraints_with_markers_missing_one_marker(self): - constraints_content = textwrap.dedent(""" - name==1.1;python_version=='2.7' - name==2.0;python_version=='3.5' - """) - project_data = { - 'requirements': { - 'requirements.txt': textwrap.dedent(""" - name>=1.1,!=1.2;python_version=='2.7' - name>=2.0;python_version=='3.5' - name>=2.0;python_version=='3.6' - """), - }, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertTrue( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - def test_complex_marker_evaluation(self): - constraints_content = textwrap.dedent(""" - name===0.8.0;python_version=='2.7' - name===1.0.0;python_version>='3.0' - """) - project_data = { - 'requirements': { - 'requirements.txt': textwrap.dedent(""" - name>=0.8.0;python_version<'3.0' # BSD - name>=1.0.0;python_version>='3.0' # BSD - """), - }, - 'lower-constraints.txt': constraints_content, - } - head_reqs = check.RequirementsList('testproj', project_data) - head_reqs.process(False) - self.assertFalse( - check.validate_lower_constraints( - req_list=head_reqs, - constraints=project_data['lower-constraints.txt'], - denylist=requirement.parse(''), - ) - ) - - class TestBackportPythonMarkers(testtools.TestCase): def setUp(self): diff --git a/openstack_requirements/tests/test_project.py b/openstack_requirements/tests/test_project.py index fb046d0e1..6394a1699 100644 --- a/openstack_requirements/tests/test_project.py +++ b/openstack_requirements/tests/test_project.py @@ -42,8 +42,8 @@ def test_no_setup_py(self): root = self.useFixture(fixtures.TempDir()).path proj = project.read(root) self.expectThat( - proj, matchers.Equals({'root': root, 'requirements': {}, - 'lower-constraints.txt': None})) + proj, matchers.Equals({'root': root, 'requirements': {}}) + ) class TestProjectExtras(testtools.TestCase): diff --git a/playbooks/files/project-requirements-change.py b/playbooks/files/project-requirements-change.py index 57362666c..551053a85 100755 --- a/playbooks/files/project-requirements-change.py +++ b/playbooks/files/project-requirements-change.py @@ -145,15 +145,6 @@ def main(): allow_3_only=python_3_branch, ) - failed = ( - check.validate_lower_constraints( - head_reqs, - head_proj['lower-constraints.txt'], - denylist, - ) - or failed - ) - # report the results if failed or head_reqs.failed: print("*** Incompatible requirement found!") From 2d88770535d5a985a461625fe6443039932671c3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Dec 2023 15:44:45 +0000 Subject: [PATCH 2/9] Replace use of distutils.version This is gone in Python 3.12. Ordering is slightly different with release candidates now (correctly, IMO) placed ahead of general releases so we have to update a single entry. codesearch.o.o [1] suggests this shouldn't affect anyone. [1] https://codesearch.opendev.org/?q=pysaml2!%3D4.0.3 Change-Id: I4cde27f048889dd557c0473138bd8a75c1e1cc58 Signed-off-by: Stephen Finucane --- global-requirements.txt | 2 +- openstack_requirements/requirement.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/global-requirements.txt b/global-requirements.txt index 59f845724..a23903363 100644 --- a/global-requirements.txt +++ b/global-requirements.txt @@ -132,7 +132,7 @@ PyMySQL # MIT License pyOpenSSL # Apache-2.0 pyparsing # MIT pyroute2!=0.5.4,!=0.5.5,!=0.7.1,!=0.9.1,!=0.9.2,!=0.9.3,!=0.9.4;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) -pysaml2!=4.0.3,!=4.0.4,!=4.0.5,!=4.0.5rc1,!=4.1.0,!=4.2.0,!=4.3.0,!=4.4.0,!=4.6.0 # Apache-2.0 +pysaml2!=4.0.3,!=4.0.4,!=4.0.5rc1,!=4.0.5,!=4.1.0,!=4.2.0,!=4.3.0,!=4.4.0,!=4.6.0 # Apache-2.0 pysnmp-lextudio # BSD pystache # MIT # Only required for sasl/binary protocol diff --git a/openstack_requirements/requirement.py b/openstack_requirements/requirement.py index dd6aaac36..da94db524 100644 --- a/openstack_requirements/requirement.py +++ b/openstack_requirements/requirement.py @@ -15,8 +15,8 @@ # This module has no IO at all, and none should be added. import collections -import distutils.version import packaging.specifiers +import packaging.version import pkg_resources import re @@ -26,7 +26,7 @@ def key_specifier(a): '===': 1, '==': 1, '~=': 1, '!=': 1, '<': 2, '<=': 2} a = a._spec - return (weight[a[0]], distutils.version.LooseVersion(a[1])) + return (weight[a[0]], packaging.version.parse(a[1])) class Requirement(collections.namedtuple('Requirement', From 63a179077672327ccd02e34010a74f15e9d9eea9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Sep 2025 15:49:19 +0100 Subject: [PATCH 3/9] Remove check-conflicts script This script made sense back before pip had a proper dependency resolver, but since pip 20.3 [1] this is no longer the case. Our jobs to test this can simply install the dependencies and let pip complain if it cannot resolve things. [1] https://discuss.python.org/t/announcement-pip-20-3-release/5948 Change-Id: Ifc30478dfffd52f126e0ad7535468cdd0b1cd36a Signed-off-by: Stephen Finucane --- .../cmds/check_conflicts.py | 75 ------------------- setup.cfg | 1 - tox.ini | 8 +- 3 files changed, 4 insertions(+), 80 deletions(-) delete mode 100644 openstack_requirements/cmds/check_conflicts.py diff --git a/openstack_requirements/cmds/check_conflicts.py b/openstack_requirements/cmds/check_conflicts.py deleted file mode 100644 index fbea72b6f..000000000 --- a/openstack_requirements/cmds/check_conflicts.py +++ /dev/null @@ -1,75 +0,0 @@ -# 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. - -"""Apply validation rules to the various requirements lists. - -""" - -import argparse -import sys -import traceback - -import pkg_resources - -from openstack_requirements.utils import read_requirements_file - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - 'upper_constraints', - default='upper-constraints.txt', - help='path to the upper-constraints.txt file') - parser.add_argument( - 'uc_xfails', - default='upper-constraints-xfails.txt', - help='Path to the upper-constraints-xfails.txt file', - ) - args = parser.parse_args() - - error_count = 0 - - print('\nChecking %s' % args.upper_constraints) - upper_constraints = read_requirements_file(args.upper_constraints) - xfails = read_requirements_file(args.uc_xfails) - for name, spec_list in upper_constraints.items(): - try: - if name: - pyver = "python_version=='%s.%s'" % (sys.version_info[0], - sys.version_info[1]) - for req, original_line in spec_list: - if req.markers in ["", pyver]: - pkg_resources.require(name) - except pkg_resources.ContextualVersionConflict as e: - if e.dist.key in xfails: - xfail_requirement = xfails[e.dist.key][0][0] - xfail_denylists = set(xfail_requirement.markers.split(',')) - conflict = e.dist.as_requirement() - conflict_specifiers = ''.join(conflict.specs[0]) - conflict_name = conflict.name.lower() - - if (e.required_by.issubset(xfail_denylists) and - xfail_requirement.package == conflict_name and - conflict_specifiers == xfail_requirement.specifiers): - - print('XFAIL while checking conflicts ' - 'for %s: %s conflicts with %s' % - (name, e.dist, str(e.req))) - continue - - print('Checking conflicts for %s:\n' - 'ContextualVersionConflict: %s' % (name, str(e))) - - traceback.print_exc(file=sys.stdout) - error_count += 1 - - return 1 if error_count else 0 diff --git a/setup.cfg b/setup.cfg index af56b935d..0235e9647 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,6 @@ packages = console_scripts = edit-constraints = openstack_requirements.cmds.edit_constraint:main generate-constraints = openstack_requirements.cmds.generate:main - check-conflicts = openstack_requirements.cmds.check_conflicts:main validate-constraints = openstack_requirements.cmds.validate:main validate-projects = openstack_requirements.cmds.validate_projects:main normalize-requirements = openstack_requirements.cmds.normalize_requirements:main diff --git a/tox.ini b/tox.ini index d0a0fc3c9..6bc3f9ee8 100644 --- a/tox.ini +++ b/tox.ini @@ -14,22 +14,22 @@ commands = [testenv:py310-check-uc] basepython = python3.10 deps = -r{toxinidir}/upper-constraints.txt -commands = check-conflicts {toxinidir}/upper-constraints.txt {toxinidir}/upper-constraints-xfails.txt +commands = python -c 'print("done")' [testenv:py311-check-uc] basepython = python3.11 deps = -r{toxinidir}/upper-constraints.txt -commands = check-conflicts {toxinidir}/upper-constraints.txt {toxinidir}/upper-constraints-xfails.txt +commands = python -c 'print("done")' [testenv:py312-check-uc] basepython = python3.12 deps = -r{toxinidir}/upper-constraints.txt -commands = check-conflicts {toxinidir}/upper-constraints.txt {toxinidir}/upper-constraints-xfails.txt +commands = python -c 'print("done")' [testenv:py313-check-uc] basepython = python3.13 deps = -r{toxinidir}/upper-constraints.txt -commands = check-conflicts {toxinidir}/upper-constraints.txt {toxinidir}/upper-constraints-xfails.txt +commands = python -c 'print("done")' [testenv:venv] commands = {posargs} From 4eb5ace3a17900ca0c2fa54fa180b3aa299121cc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Sep 2025 17:14:06 +0100 Subject: [PATCH 4/9] Speed up pyN-check-uc jobs ...by skipping actual installation. We only need to check if they are co-installable via metadata. We don't need to actually install them. This will make the job much easier to run in environments where bindep hasn't been run. Change-Id: Iaa7f02ec9d1e734884b1329e2b177964f54d64df Signed-off-by: Stephen Finucane --- tox.ini | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tox.ini b/tox.ini index 6bc3f9ee8..546eeaa9c 100644 --- a/tox.ini +++ b/tox.ini @@ -13,23 +13,27 @@ commands = [testenv:py310-check-uc] basepython = python3.10 -deps = -r{toxinidir}/upper-constraints.txt -commands = python -c 'print("done")' +skip_install = true +deps = +commands = python -m pip install --dry-run -r{toxinidir}/upper-constraints.txt [testenv:py311-check-uc] basepython = python3.11 -deps = -r{toxinidir}/upper-constraints.txt -commands = python -c 'print("done")' +skip_install = true +deps = +commands = {[testenv:py310-check-uc]commands} [testenv:py312-check-uc] basepython = python3.12 -deps = -r{toxinidir}/upper-constraints.txt -commands = python -c 'print("done")' +skip_install = true +deps = +commands = {[testenv:py310-check-uc]commands} [testenv:py313-check-uc] basepython = python3.13 -deps = -r{toxinidir}/upper-constraints.txt -commands = python -c 'print("done")' +skip_install = true +deps = +commands = {[testenv:py310-check-uc]commands} [testenv:venv] commands = {posargs} From 8c8f62e1fcf480e61972504309c95e8d84be148a Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Wed, 22 Oct 2025 17:00:59 +0000 Subject: [PATCH 5/9] Add line separators to warning comment The writelines method doesn't include newline characters automatically, so add them to the literal strings. Change-Id: I6677b19594d101f239542e24a70935639f2d04e7 Signed-off-by: Jeremy Stanley --- openstack_requirements/cmds/generate.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openstack_requirements/cmds/generate.py b/openstack_requirements/cmds/generate.py index 43090832b..0ad2e6280 100644 --- a/openstack_requirements/cmds/generate.py +++ b/openstack_requirements/cmds/generate.py @@ -27,13 +27,13 @@ SECURITY_WARNING = [ - "# WARNING: OpenStack makes no security guarantees about third-party", - "# dependencies listed here, and does not keep track of any", - "# vulnerabilities they contain. Versions of these dependencies are", - "# frozen at each coordinated release in order to stabilize upstream", - "# testing, and can contain known vulnerabilities. Consumers are", - "# *STRONGLY* encouraged to rely on curated distributions of OpenStack", - "# or manage security patching of dependencies themselves.", + "# WARNING: OpenStack makes no security guarantees about third-party\n", + "# dependencies listed here, and does not keep track of any\n", + "# vulnerabilities they contain. Versions of these dependencies are\n", + "# frozen at each coordinated release in order to stabilize upstream\n", + "# testing, and can contain known vulnerabilities. Consumers are\n", + "# *STRONGLY* encouraged to rely on curated distributions of OpenStack\n", + "# or manage security patching of dependencies themselves.\n", ] From e767f28c26872e7147f18b3c0b4d3ddbf7b6eda7 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 23 Oct 2025 10:30:25 +0100 Subject: [PATCH 6/9] Skip comments, empty lines when parsing reqs Change-Id: I065ad829ef8aa05d74045a985526256f8056bb20 Signed-off-by: Stephen Finucane --- openstack_requirements/requirement.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openstack_requirements/requirement.py b/openstack_requirements/requirement.py index da94db524..56ba2c0d6 100644 --- a/openstack_requirements/requirement.py +++ b/openstack_requirements/requirement.py @@ -179,6 +179,11 @@ def _pass_through(req_line, permit_urls=False): def to_reqs(content, permit_urls=False): for content_line in content.splitlines(True): req_line = content_line.strip() + + # skip comments, blank lines + if req_line.startswith('#') or not req_line: + continue + if _pass_through(req_line, permit_urls=permit_urls): yield None, content_line else: From 6b3dda33d305df109b9cb50e7c0a9de6bd6dd567 Mon Sep 17 00:00:00 2001 From: Tony Breeds Date: Fri, 24 Oct 2025 10:34:20 +1100 Subject: [PATCH 7/9] [check-uc*] Create essentially a "no-op" dependency This works around an unknown aspect of tox in that until you do something (install packages, or run commands) tox doesn't create .tox/{envdir}/log. This isn't a big deal except tox_install_sibling_packages[1] relies on that existing. If it doesn't we end up with a module failure: Change-Id: I3824235b49b77e84e693a8a37c9ff3a1027a3acd --- 2025-10-23 21:38:45.404140 | TASK [tox : Install any sibling python packages] 2025-10-23 21:38:46.846253 | ubuntu-jammy | MODULE FAILURE: 2025-10-23 21:38:46.847689 | ubuntu-jammy | FileNotFoundError: [Errno 2] No such file or directory: '/home/zuul/src/opendev.org/openstack/requirements/.tox/py310-check-uc/log/py310-check-uc-siblings.txt' --- Until that module is fixed add 'setuptools' as a dependency. It will be installed already as part of the virtualenv so is funtionally equivilent to an empty set of deps. This is a enhancement? to Iaa7f02ec9d1e734884b1329e2b177964f54d64df Also updated upper-constraints.txt to ensure the check-uc* jobs run [1] https://opendev.org/zuul/zuul-jobs/src/branch/master/roles/tox/library/tox_install_sibling_packages.py#L387 Change-Id: I7fd709d0cf0468c90593b5f63bb34d58e9c1e774 Signed-off-by: Tony Breeds --- tox.ini | 16 ++++++++++++---- upper-constraints.txt | 7 +++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 546eeaa9c..39badc67a 100644 --- a/tox.ini +++ b/tox.ini @@ -14,25 +14,33 @@ commands = [testenv:py310-check-uc] basepython = python3.10 skip_install = true -deps = +# For now we need something due to an issue in the tox_install_sibling_packages +# AnseibleModule +deps = setuptools commands = python -m pip install --dry-run -r{toxinidir}/upper-constraints.txt [testenv:py311-check-uc] basepython = python3.11 skip_install = true -deps = +# For now we need something due to an issue in the tox_install_sibling_packages +# AnseibleModule +deps = setuptools commands = {[testenv:py310-check-uc]commands} [testenv:py312-check-uc] basepython = python3.12 skip_install = true -deps = +# For now we need something due to an issue in the tox_install_sibling_packages +# AnseibleModule +deps = setuptools commands = {[testenv:py310-check-uc]commands} [testenv:py313-check-uc] basepython = python3.13 skip_install = true -deps = +# For now we need something due to an issue in the tox_install_sibling_packages +# AnseibleModule +deps = setuptools commands = {[testenv:py310-check-uc]commands} [testenv:venv] diff --git a/upper-constraints.txt b/upper-constraints.txt index 03c348fb3..6efbd2503 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -1,3 +1,10 @@ +# WARNING: OpenStack makes no security guarantees about third-party +# dependencies listed here, and does not keep track of any +# vulnerabilities they contain. Versions of these dependencies are +# frozen at each coordinated release in order to stabilize upstream +# testing, and can contain known vulnerabilities. Consumers are +# *STRONGLY* encouraged to rely on curated distributions of OpenStack +# or manage security patching of dependencies themselves. voluptuous===0.15.2 chardet===5.2.0 enum-compat===0.0.3 From 856df5bd49cd2af983fcda85001ebb79e812f228 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 9 Oct 2025 10:20:54 +0000 Subject: [PATCH 8/9] update constraint for yaql to new release 3.2.0 meta: version: 3.2.0 meta: team: heat meta: diff-start: - meta: series: independent meta: branch: master meta: release-type: release meta: pypi: yes meta: first: no meta: release:Author: Takashi Kajinami meta: release:Commit: Takashi Kajinami meta: release:Change-Id: Ia38dd74d7e5725a387e9fdcf08cd66fa7fec19f3 meta: release:Code-Review+2: Elod Illes meta: release:Workflow+1: Thierry Carrez meta: release:Code-Review+2: Thierry Carrez Change-Id: I86b65de76fb46f40d2b91437e866773357b1d96c Signed-off-by: OpenStack Proposal Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/update_constraints.sh --- upper-constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upper-constraints.txt b/upper-constraints.txt index 6efbd2503..47c198648 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -479,7 +479,7 @@ sentinels===1.0.0 kombu===5.5.4 distro===1.9.0 zstd===1.5.7.2 -yaql===3.1.0 +yaql===3.2.0 durationpy===0.10 requestsexceptions===1.4.0 testresources===2.0.2 From 756a1a94446d58ba06c907c1ec578182d0be8943 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Thu, 4 Sep 2025 17:53:10 +0200 Subject: [PATCH 9/9] Pin boto3 and botocore to 1.35.99 Starting with 1.36.0[1], boto3 implements a new security feature that may not be implemented by some S3-compatible storage backends, such as Ceph RGW. This causes glance_store users to notice failures when uploading images to such backends[2]. This might also impact other projects such as Cinder. Pinning boto3 to 1.35.99 until we come up with a proper workaround for this. Also pinning botocore to 1.35.99 as this is the only version that is compatible with boto3 1.35.99, and pinning s3transfer to 0.10.0 as this is the only version that is compatible with botocore 1.35.99. (tonyb) As this is potentially a longer term issue, include the caps in global-requirements.txt to ease constraints updates. [1] https: //github.com/boto/boto3/blob/dd74a1e97c450cf7670cabf3a9516b5127bcffb5/CHANGELOG.rst#L1882 [2] https://bugs.launchpad.net/glance/+bug/2121144 Change-Id: I311a74d6839410779f89f3bde817c221d9d8344e Signed-off-by: Cyril Roelandt Signed-off-by: Tony Breeds --- global-requirements.txt | 7 +++++-- upper-constraints.txt | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/global-requirements.txt b/global-requirements.txt index 0e779b5d6..0619639df 100644 --- a/global-requirements.txt +++ b/global-requirements.txt @@ -24,8 +24,11 @@ bcrypt==4.0.1 # Apache-2.0 beautifulsoup4 # MIT betamax # Apache-2.0 boto # MIT -boto3 # Apache-2.0 -botocore # Apache-2.0 +# Capped until https://bugs.launchpad.net/glance/+bug/2121144 is resolved +boto3<1.36 # Apache-2.0 +botocore<1.36 # Apache-2.0 +# indirect from boto3/botocore +s3transfer<0.11 # Apache-2.0 cachetools # MIT License cassandra-driver!=3.6.0 # Apache-2.0 cffi # MIT diff --git a/upper-constraints.txt b/upper-constraints.txt index 6efbd2503..64e1082bd 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -315,7 +315,7 @@ retrying===1.4.1 XStatic-Dagre===0.6.4.1 platformdirs===4.3.8 pydotplus===2.0.2 -boto3===1.40.1 +boto3===1.35.99 jeepney===0.9.0 stestr===4.2.0 pillow===11.3.0 @@ -328,7 +328,7 @@ aiomysql===0.2.0 types-simplejson===3.20.0.20250326 sphinxcontrib-httpdomain===1.8.1 metalsmith===2.5.0 -s3transfer===0.13.1 +s3transfer===0.10.0 text-unidecode===1.3 sphinxcontrib-svg2pdfconverter===1.3.0 oslo.vmware===4.7.0 @@ -445,7 +445,7 @@ tomli===2.2.1;python_version=='3.9' oslo.upgradecheck===2.6.0 sherlock===0.4.1 stevedore===5.5.0 -botocore===1.40.1 +botocore===1.35.99 xmltodict===0.14.2 pyasn1===0.6.0 oslo.rootwrap===7.7.0