From 206c47fc7da35c33069f719c3091cad42ee64492 Mon Sep 17 00:00:00 2001 From: Brett Holman Date: Mon, 8 Dec 2025 22:31:46 +0000 Subject: [PATCH 1/4] chore: drop support for Python 3.9 --- .github/workflows/check_format.yml | 2 +- .github/workflows/unit.yml | 4 ++-- cloudinit/util.py | 24 ++++++++------------ doc/rtd/development/contribute_code.rst | 4 +++- integration-requirements.txt | 7 +----- requirements.txt | 2 +- test-requirements.txt | 2 +- tests/integration_tests/reaper.py | 2 -- tests/unittests/test_util.py | 29 +----------------------- tox.ini | 30 ++++++++++++------------- 10 files changed, 33 insertions(+), 73 deletions(-) diff --git a/.github/workflows/check_format.yml b/.github/workflows/check_format.yml index 3b77b2371db..b30af182c18 100644 --- a/.github/workflows/check_format.yml +++ b/.github/workflows/check_format.yml @@ -70,7 +70,7 @@ jobs: run: | git fetch --unshallow git remote add upstream https://git.launchpad.net/cloud-init - - name: "Install Python 3.10" + - name: "Install Python 3.11.9" uses: actions/setup-python@v5 with: python-version: '3.11.9' diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index a24765a209c..1603442c2dc 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -14,14 +14,14 @@ jobs: unittests: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] toxenv: [py3] slug: [""] experimental: [false] check-latest: [false] continue-on-error: [false] include: - - python-version: "3.8" + - python-version: "3.9" toxenv: lowest-supported slug: (lowest-supported) continue-on-error: false diff --git a/cloudinit/util.py b/cloudinit/util.py index f6efb851a29..9117a8d3260 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -585,21 +585,15 @@ def get_linux_distro(): distro_version = platform.release() else: dist = ("", "", "") - try: - # Was removed in 3.8 - dist = platform.dist() # type: ignore # pylint: disable=W1505,E1101 - except Exception: - pass - finally: - found = None - for entry in dist: - if entry: - found = 1 - if not found: - LOG.warning( - "Unable to determine distribution, template " - "expansion may have unexpected results" - ) + found = None + for entry in dist: + if entry: + found = 1 + if not found: + LOG.warning( + "Unable to determine distribution, template " + "expansion may have unexpected results" + ) return dist return (distro_name, distro_version, flavor) diff --git a/doc/rtd/development/contribute_code.rst b/doc/rtd/development/contribute_code.rst index 47db2485654..40e54b2d325 100644 --- a/doc/rtd/development/contribute_code.rst +++ b/doc/rtd/development/contribute_code.rst @@ -19,7 +19,7 @@ Cloud-init adheres to `PEP 8`_, and this is enforced by CI tests. Python support -------------- -Cloud-init upstream currently supports Python 3.8 and above. +Cloud-init upstream currently supports Python 3.9 and above. Cloud-init upstream will stay compatible with a particular Python version for 6 years after release. After 6 years, we will stop testing upstream changes @@ -35,6 +35,8 @@ version changed: * - Cloud-init version - Python version + * - 25.4 + - 3.9+ * - 24.3 - 3.8+ * - 22.1 diff --git a/integration-requirements.txt b/integration-requirements.txt index 0a18907f3a3..c77c14c0e40 100644 --- a/integration-requirements.txt +++ b/integration-requirements.txt @@ -4,11 +4,6 @@ # pycloudlib>=1!10.0.2,<1!11 -# Avoid breaking change in `testpaths` treatment forced -# test/unittests/conftest.py to be loaded by our integration-tests tox env -# resulting in an unmet dependency issue: -# https://github.com/pytest-dev/pytest/issues/11104 -pytest!=7.3.2 pytest-timeout # Even when xdist is not actively used, we have fixtures that require it @@ -16,4 +11,4 @@ pytest-xdist packaging passlib -coverage==7.2.7 # Last version supported in Python 3.7 +coverage==7.4.4 diff --git a/requirements.txt b/requirements.txt index 05aac105fd0..4ff92c93869 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ oauthlib # that the built-in config parser is not sufficient (ie # when we need to preserve comments, or do not have a top-level # section)... -configobj>=5.0.2 +configobj # All new style configurations are in the yaml format pyyaml diff --git a/test-requirements.txt b/test-requirements.txt index c55f9ca4b5e..9467f3d9328 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ # test/unittests/conftest.py to be loaded by our integration-tests tox env # resulting in an unmet dependency issue: # https://github.com/pytest-dev/pytest/issues/11104 -pytest!=7.3.2 +pytest pytest-cov pytest-mock diff --git a/tests/integration_tests/reaper.py b/tests/integration_tests/reaper.py index 878d712a211..e96b114188e 100644 --- a/tests/integration_tests/reaper.py +++ b/tests/integration_tests/reaper.py @@ -7,8 +7,6 @@ are reported to the end user as a test warning. """ -from __future__ import annotations # required for Python 3.8 - import logging import queue import threading diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index f2ff3fa8672..d320f17a3e2 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -1299,42 +1299,15 @@ def test_get_linux_cos(self, m_os_release, m_path_exists): assert ("cos", "93", "") == dist @mock.patch("platform.system") - @mock.patch("platform.dist", create=True) def test_get_linux_distro_no_data( - self, m_platform_dist, m_platform_system, m_path_exists + self, m_platform_system, m_path_exists ): """Verify we get no information if os-release does not exist""" - m_platform_dist.return_value = ("", "", "") m_platform_system.return_value = "Linux" m_path_exists.return_value = 0 dist = util.get_linux_distro() assert ("", "", "") == dist - @mock.patch("platform.system") - @mock.patch("platform.dist", create=True) - def test_get_linux_distro_no_impl( - self, m_platform_dist, m_platform_system, m_path_exists - ): - """Verify we get an empty tuple when no information exists and - Exceptions are not propagated""" - m_platform_dist.side_effect = Exception() - m_platform_system.return_value = "Linux" - m_path_exists.return_value = 0 - dist = util.get_linux_distro() - assert ("", "", "") == dist - - @mock.patch("platform.system") - @mock.patch("platform.dist", create=True) - def test_get_linux_distro_plat_data( - self, m_platform_dist, m_platform_system, m_path_exists - ): - """Verify we get the correct platform information""" - m_platform_dist.return_value = ("foo", "1.1", "aarch64") - m_platform_system.return_value = "Linux" - m_path_exists.return_value = 0 - dist = util.get_linux_distro() - assert ("foo", "1.1", "aarch64") == dist - class TestGetVariant: @pytest.mark.parametrize( diff --git a/tox.ini b/tox.ini index 4e801689577..16795db47a7 100644 --- a/tox.ini +++ b/tox.ini @@ -171,24 +171,22 @@ commands = coverage report -i # dependencies will generally be older than what is found in pip. # To obtain these versions, check the versions of these libraries -# in the oldest support Ubuntu distro. These versions are from bionic. +# in the oldest support Ubuntu distro. These versions are from noble. deps = - jinja2==2.10.1 - oauthlib==3.1.0 - pyserial==3.4 - configobj==5.0.6 - pyyaml==5.3.1 - requests==2.22.0 - jsonpatch==1.23 - jsonschema==3.2.0 + jinja2==3.1.2 + oauthlib==3.2.2 + pyserial==3.5 + configobj==5.0.8 + pyyaml==6.0.1 + requests==2.31.0 + jsonpatch==1.32.0 + jsonschema==4.10.3 # test-requirements - pytest==4.6.9 - pytest-cov==2.8.1 - pytest-mock==1.10.4 - responses==0.9.0 - passlib - # required for this version of jinja2 - markupsafe==2.0.1 + pytest==7.4.4 + pytest-cov==4.1.0 + pytest-mock==3.12.0 + responses==0.24.1 + passlib==1.7.4 commands = {envpython} -m pytest -m "not hypothesis_slow" --cov=cloud-init --cov-branch {posargs:tests/unittests} [testenv:doc] From a62d56a48a48a0f312ef065fa9a1ed81b2a8fef8 Mon Sep 17 00:00:00 2001 From: Brett Holman Date: Mon, 8 Dec 2025 22:35:29 +0000 Subject: [PATCH 2/4] remove redundant language --- doc/rtd/development/contribute_code.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/rtd/development/contribute_code.rst b/doc/rtd/development/contribute_code.rst index 40e54b2d325..2818ac24bb1 100644 --- a/doc/rtd/development/contribute_code.rst +++ b/doc/rtd/development/contribute_code.rst @@ -22,9 +22,8 @@ Python support Cloud-init upstream currently supports Python 3.9 and above. Cloud-init upstream will stay compatible with a particular Python version for 6 -years after release. After 6 years, we will stop testing upstream changes +years after release. After that, upstream will stop testing upstream changes against the unsupported version of Python and may introduce breaking changes. -This policy may change as needed. The following table lists the cloud-init versions in which the minimum Python version changed: From 0fb7b0aa9b2808aa3a2f2eca267c6568653e5827 Mon Sep 17 00:00:00 2001 From: Brett Holman Date: Mon, 8 Dec 2025 22:35:54 +0000 Subject: [PATCH 3/4] black --- tests/unittests/test_util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index d320f17a3e2..0585234c676 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -1299,9 +1299,7 @@ def test_get_linux_cos(self, m_os_release, m_path_exists): assert ("cos", "93", "") == dist @mock.patch("platform.system") - def test_get_linux_distro_no_data( - self, m_platform_system, m_path_exists - ): + def test_get_linux_distro_no_data(self, m_platform_system, m_path_exists): """Verify we get no information if os-release does not exist""" m_platform_system.return_value = "Linux" m_path_exists.return_value = 0 From d55635dd6fd28987966b1d1e2a658bfc86886216 Mon Sep 17 00:00:00 2001 From: Brett Holman Date: Wed, 10 Dec 2025 14:26:52 +0000 Subject: [PATCH 4/4] jammy not noble --- cloudinit/util.py | 16 +++++----------- integration-requirements.txt | 2 +- tox.ini | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/cloudinit/util.py b/cloudinit/util.py index 9117a8d3260..b76b58077b0 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -584,17 +584,11 @@ def get_linux_distro(): distro_name = platform.system().lower() distro_version = platform.release() else: - dist = ("", "", "") - found = None - for entry in dist: - if entry: - found = 1 - if not found: - LOG.warning( - "Unable to determine distribution, template " - "expansion may have unexpected results" - ) - return dist + LOG.warning( + "Unable to determine distribution, template " + "expansion may have unexpected results" + ) + return "", "", "" return (distro_name, distro_version, flavor) diff --git a/integration-requirements.txt b/integration-requirements.txt index c77c14c0e40..d0db753dabc 100644 --- a/integration-requirements.txt +++ b/integration-requirements.txt @@ -11,4 +11,4 @@ pytest-xdist packaging passlib -coverage==7.4.4 +coverage diff --git a/tox.ini b/tox.ini index 16795db47a7..44ed92d6624 100644 --- a/tox.ini +++ b/tox.ini @@ -171,21 +171,21 @@ commands = coverage report -i # dependencies will generally be older than what is found in pip. # To obtain these versions, check the versions of these libraries -# in the oldest support Ubuntu distro. These versions are from noble. +# in the oldest support Ubuntu distro. These versions are from Jammy. deps = - jinja2==3.1.2 - oauthlib==3.2.2 + jinja2==3.0.3 + oauthlib==3.2.0 pyserial==3.5 - configobj==5.0.8 - pyyaml==6.0.1 - requests==2.31.0 + configobj==5.0.6 + pyyaml==5.4.1 + requests==2.25.1 jsonpatch==1.32.0 - jsonschema==4.10.3 + jsonschema==3.2.0 # test-requirements - pytest==7.4.4 - pytest-cov==4.1.0 - pytest-mock==3.12.0 - responses==0.24.1 + pytest==6.2.5 + pytest-cov==3.0.0 + pytest-mock==3.6.1 + responses==0.18.0 passlib==1.7.4 commands = {envpython} -m pytest -m "not hypothesis_slow" --cov=cloud-init --cov-branch {posargs:tests/unittests}