diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f4e27ab10e..5461a64c60 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,9 @@ updates: schedule: interval: "daily" time: "02:00" + # requirements.txt is the install_requires of a published library, so + # widen ranges instead of raising lower bounds on every new release. + versioning-strategy: "widen" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/e2e-master.yaml b/.github/workflows/e2e-master.yaml index 588d369cd0..73102f968e 100644 --- a/.github/workflows/e2e-master.yaml +++ b/.github/workflows/e2e-master.yaml @@ -19,7 +19,7 @@ jobs: with: submodules: true - name: Create Kind Cluster - uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 + uses: helm/kind-action@1544676c07bb3570fcb70166beaa07c4528fd4aa with: cluster_name: kubernetes-python-e2e-master-${{ matrix.python-version }} # The kind version to be used to spin the cluster up @@ -30,7 +30,7 @@ jobs: # as we sync with Kubernetes upstream config: .github/workflows/kind-configs/cluster-1.35.yaml - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/e2e-release-35.0.yaml b/.github/workflows/e2e-release-35.0.yaml index 730710b2f2..3efe8149d6 100644 --- a/.github/workflows/e2e-release-35.0.yaml +++ b/.github/workflows/e2e-release-35.0.yaml @@ -19,7 +19,7 @@ jobs: with: submodules: true - name: Create Kind Cluster - uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 + uses: helm/kind-action@1544676c07bb3570fcb70166beaa07c4528fd4aa with: cluster_name: kubernetes-python-e2e-release-35.0-${{ matrix.python-version }} # The kind version to be used to spin the cluster up @@ -30,7 +30,7 @@ jobs: # as we sync with Kubernetes upstream config: .github/workflows/kind-configs/cluster-1.35.yaml - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 692d488ff8..f22e90fa7f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: with: submodules: true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 266e93a511..08c2abd4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v37.0.2 + +Kubernetes API Version: v1.37.2 + + # v37.0.1 Kubernetes API Version: v1.37.1 diff --git a/README.md b/README.md index 26106dfad4..c1b759aab6 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ supported versions of Kubernetes clusters. - [client 34.y.z](https://pypi.org/project/kubernetes/34.1.0/): Kubernetes 1.33 or below (+-), Kubernetes 1.34 (✓), Kubernetes 1.35 or above (+-) - [client 35.y.z](https://pypi.org/project/kubernetes/35.0.0/): Kubernetes 1.34 or below (+-), Kubernetes 1.35 (✓), Kubernetes 1.36 or above (+-) - [client 36.y.z](https://pypi.org/project/kubernetes/36.0.3/): Kubernetes 1.35 or below (+-), Kubernetes 1.36 (✓), Kubernetes 1.37 or above (+-) -- [client 37.y.z](https://pypi.org/project/kubernetes/37.0.1/): Kubernetes 1.36 or below (+-), Kubernetes 1.37 (✓), Kubernetes 1.38 or above (+-) +- [client 37.y.z](https://pypi.org/project/kubernetes/37.0.2/): Kubernetes 1.36 or below (+-), Kubernetes 1.37 (✓), Kubernetes 1.38 or above (+-) > See [here](#homogenizing-the-kubernetes-python-client-versions) for an explanation of why there is no v13-v16 release. diff --git a/kubernetes/README.md b/kubernetes/README.md index 9dd265b58d..69275345af 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -4,7 +4,7 @@ No description provided (generated by Openapi Generator https://github.com/opena This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: release-1.37 -- Package version: 37.0.1 +- Package version: 37.0.2 - Generator version: 7.25.0 - Build package: org.openapitools.codegen.languages.PythonClientCodegen diff --git a/kubernetes/__init__.py b/kubernetes/__init__.py index 0ac0625346..7b2b1c6997 100644 --- a/kubernetes/__init__.py +++ b/kubernetes/__init__.py @@ -14,7 +14,7 @@ __project__ = 'kubernetes' # The version is auto-updated. Please do not edit. -__version__ = "37.0.1" +__version__ = "37.0.2" from . import client from . import config diff --git a/kubernetes/aio/README.md b/kubernetes/aio/README.md index 2106e67f24..47ad1f63d0 100644 --- a/kubernetes/aio/README.md +++ b/kubernetes/aio/README.md @@ -4,7 +4,7 @@ No description provided (generated by Openapi Generator https://github.com/opena This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: - API version: release-1.37 -- Package version: 37.0.1 +- Package version: 37.0.2 - Generator version: 7.25.0 - Build package: org.openapitools.codegen.languages.PythonClientCodegen diff --git a/kubernetes/aio/__init__.py b/kubernetes/aio/__init__.py index 35d685a78e..d8d4cee972 100644 --- a/kubernetes/aio/__init__.py +++ b/kubernetes/aio/__init__.py @@ -14,7 +14,7 @@ __project__ = "kubernetes_aio" # The version is auto-updated. Please do not edit. -__version__ = "37.0.1" +__version__ = "37.0.2" import kubernetes.aio.client as client import kubernetes.aio.config as config diff --git a/kubernetes/aio/client/__init__.py b/kubernetes/aio/client/__init__.py index c60b21d880..1c905d3fb7 100644 --- a/kubernetes/aio/client/__init__.py +++ b/kubernetes/aio/client/__init__.py @@ -14,7 +14,7 @@ """ # noqa: E501 -__version__ = "37.0.1" +__version__ = "37.0.2" # Define package exports __all__ = [ diff --git a/kubernetes/aio/client/api_client.py b/kubernetes/aio/client/api_client.py index 5d0cafb9e4..36b657ff3f 100644 --- a/kubernetes/aio/client/api_client.py +++ b/kubernetes/aio/client/api_client.py @@ -122,7 +122,7 @@ def __init__( self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/37.0.1/python' + self.user_agent = 'OpenAPI-Generator/37.0.2/python' self.client_side_validation = configuration.client_side_validation async def __aenter__(self): diff --git a/kubernetes/aio/client/configuration.py b/kubernetes/aio/client/configuration.py index cf052c2dfc..abb2b49c7a 100644 --- a/kubernetes/aio/client/configuration.py +++ b/kubernetes/aio/client/configuration.py @@ -612,7 +612,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: release-1.37\n"\ - "SDK Package Version: 37.0.1".\ + "SDK Package Version: 37.0.2".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/kubernetes/client/__init__.py b/kubernetes/client/__init__.py index 8a9f8c2f04..a643ddce03 100644 --- a/kubernetes/client/__init__.py +++ b/kubernetes/client/__init__.py @@ -14,7 +14,7 @@ """ # noqa: E501 -__version__ = "37.0.1" +__version__ = "37.0.2" # Define package exports __all__ = [ diff --git a/kubernetes/client/api_client.py b/kubernetes/client/api_client.py index 7f71c4b32e..bed517449c 100644 --- a/kubernetes/client/api_client.py +++ b/kubernetes/client/api_client.py @@ -129,7 +129,7 @@ def __init__( self.pool_threads = pool_threads self._pool_lock = Lock() # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/37.0.1/python' + self.user_agent = 'OpenAPI-Generator/37.0.2/python' self.client_side_validation = configuration.client_side_validation def close(self): diff --git a/kubernetes/client/configuration.py b/kubernetes/client/configuration.py index ba2d428622..770788ae15 100644 --- a/kubernetes/client/configuration.py +++ b/kubernetes/client/configuration.py @@ -613,7 +613,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: release-1.37\n"\ - "SDK Package Version: 37.0.1".\ + "SDK Package Version: 37.0.2".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index a1789ae7fe..ff9ebb35da 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -688,8 +688,16 @@ def test_parse_quantity(self): self.assertRaises( ValueError, lambda: quantity.parse_quantity("1000ki") ) + # "m", "n" and "u" are base 1000 only; there is no mi, ni or ui + for invalid in ("1mi", "1ni", "1ui"): + self.assertRaises( + ValueError, lambda: quantity.parse_quantity(invalid) + ) self.assertRaises(ValueError, lambda: quantity.parse_quantity("1000foo")) self.assertRaises(ValueError, lambda: quantity.parse_quantity("foo")) + self.assertRaises( + ValueError, lambda: quantity.parse_quantity("2.e388635141Ki") + ) # == no suffix == self.assertEqual(quantity.parse_quantity("1000"), Decimal(1000)) @@ -729,6 +737,10 @@ def test_format_quantity(self): self.assertRaises( ValueError, lambda: quantity.format_quantity(Decimal(1_000), "ki") ) + for invalid in ("mi", "ni", "ui"): + self.assertRaises( + ValueError, lambda: quantity.format_quantity(Decimal(1_000), invalid) + ) self.assertRaises( ValueError, lambda: quantity.format_quantity(Decimal(1_000), "foo") ) diff --git a/kubernetes/stream/ws_client.py b/kubernetes/stream/ws_client.py index 9765f697da..0692ff180d 100644 --- a/kubernetes/stream/ws_client.py +++ b/kubernetes/stream/ws_client.py @@ -599,8 +599,9 @@ def websocket_proxycare(connect_opt, configuration, url, headers): for key,value in configuration.proxy_headers.items(): if key == 'proxy-authorization' and value.startswith('Basic'): b64value = value.split()[1] - auth = urlsafe_b64decode(b64value).decode().split(':') - connect_opt.update({'http_proxy_auth': (auth[0], auth[1]) }) + auth = urlsafe_b64decode(b64value).decode().split(':', 1) + if len(auth) == 2: + connect_opt.update({'http_proxy_auth': (auth[0], auth[1]) }) return(connect_opt) diff --git a/kubernetes/stream/ws_client_test.py b/kubernetes/stream/ws_client_test.py index 7e1edfbe28..7a31dea872 100644 --- a/kubernetes/stream/ws_client_test.py +++ b/kubernetes/stream/ws_client_test.py @@ -111,6 +111,8 @@ def test_websocket_proxycare(self): ( 'http://proxy.example.com:8080/', 'user:pass', '*', 'proxy.example.com', 8080, ('user','pass'), ['*']), ( 'http://proxy.example.com:8080/', 'user:pass', '.example.com', 'proxy.example.com', 8080, ('user','pass'), ['.example.com']), ( 'http://proxy.example.com:8080/', 'user:pass', 'localhost,.local,.example.com', 'proxy.example.com', 8080, ('user','pass'), ['localhost','.local','.example.com']), + ( 'http://proxy.example.com:8080/', 'user:pa:ss:word', None, 'proxy.example.com', 8080, ('user','pa:ss:word'), None), + ( 'http://proxy.example.com:8080/', 'useronly', None, 'proxy.example.com', 8080, None, None), ]: # input setup cfg = Configuration(proxy='', no_proxy='') diff --git a/kubernetes/utils/keepalive_test.py b/kubernetes/utils/keepalive_test.py index 005808fb83..7b882b80fa 100644 --- a/kubernetes/utils/keepalive_test.py +++ b/kubernetes/utils/keepalive_test.py @@ -25,6 +25,14 @@ from kubernetes.utils.keepalive import tcp_keepalive_socket_options +# Linux spells the idle time TCP_KEEPIDLE, macOS spells it TCP_KEEPALIVE, and +# some platforms have neither (keepalive.py skips the option there). +IDLE_OPTION = getattr(socket, 'TCP_KEEPIDLE', + getattr(socket, 'TCP_KEEPALIVE', None)) +needs_idle_option = unittest.skipIf( + IDLE_OPTION is None, 'platform has no TCP keepalive idle option') + + def fake_socket_module(**names): """A stand-in for the socket module exposing only the given names.""" @@ -39,12 +47,13 @@ def fake_socket_module(**names): class TestTcpKeepaliveSocketOptions(unittest.TestCase): + @needs_idle_option def test_defaults_match_client_go(self): options = tcp_keepalive_socket_options() self.assertIn((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), options) self.assertIn( - (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30), options) + (socket.IPPROTO_TCP, IDLE_OPTION, 30), options) self.assertIn( (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15), options) self.assertIn((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 9), options) @@ -56,10 +65,11 @@ def test_keeps_the_urllib3_defaults(self): for default in defaults: self.assertIn(default, options) + @needs_idle_option def test_custom_timings(self): options = tcp_keepalive_socket_options(idle=5, interval=2, count=3) - self.assertIn((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 5), options) + self.assertIn((socket.IPPROTO_TCP, IDLE_OPTION, 5), options) self.assertIn((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 2), options) self.assertIn((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3), options) @@ -74,15 +84,17 @@ def test_options_are_setsockopt_triples(self): for item in option: self.assertIsInstance(item, int) + @needs_idle_option def test_options_apply_to_a_socket(self): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: for level, name, value in tcp_keepalive_socket_options(): sock.setsockopt(level, name, value) + # Boolean options read back as any non-zero value (8 on macOS) + self.assertNotEqual( + 0, sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) self.assertEqual( - 1, sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) - self.assertEqual( - 30, sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE)) + 30, sock.getsockopt(socket.IPPROTO_TCP, IDLE_OPTION)) def test_falls_back_to_tcp_keepalive_on_macos(self): macos = fake_socket_module( diff --git a/kubernetes/utils/quantity.py b/kubernetes/utils/quantity.py index a45026dbe6..5f56f00cd9 100644 --- a/kubernetes/utils/quantity.py +++ b/kubernetes/utils/quantity.py @@ -11,7 +11,7 @@ # 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 decimal import Decimal, InvalidOperation +from decimal import Decimal, DecimalException, InvalidOperation _EXPONENTS = { "n": -3, @@ -26,6 +26,9 @@ "E": 6, } +# Only these prefixes exist in base 1024 (Ki, Mi, Gi, Ti, Pi, Ei). +_BINARY_PREFIXES = "KMGTPE" + def parse_quantity(quantity): """ @@ -68,21 +71,23 @@ def parse_quantity(quantity): return number if suffix.endswith("i"): + # handle SI inconsistency: "ki", "mi", "ni" and "ui" are not valid binary suffixes + if suffix[0] not in _BINARY_PREFIXES: + raise ValueError("{} has unknown suffix".format(quantity)) base = 1024 elif len(suffix) == 1: base = 1000 else: raise ValueError("{} has unknown suffix".format(quantity)) - # handle SI inconsistency - if suffix == "ki": - raise ValueError("{} has unknown suffix".format(quantity)) - if suffix[0] not in _EXPONENTS: raise ValueError("{} has unknown suffix".format(quantity)) exponent = Decimal(_EXPONENTS[suffix[0]]) - return number * (base ** exponent) + try: + return number * (base ** exponent) + except DecimalException as exc: + raise ValueError("Invalid quantity: {}".format(quantity)) from exc def format_quantity(quantity_value, suffix, quantize=None) -> str: @@ -124,15 +129,14 @@ def format_quantity(quantity_value, suffix, quantize=None) -> str: return str(quantity_value) if suffix.endswith("i"): + if suffix[0] not in _BINARY_PREFIXES: + raise ValueError(f"{quantity_value} has unknown suffix") base = 1024 elif len(suffix) == 1: base = 1000 else: raise ValueError(f"{quantity_value} has unknown suffix") - if suffix == "ki": - raise ValueError(f"{quantity_value} has unknown suffix") - if suffix[0] not in _EXPONENTS: raise ValueError(f"{quantity_value} has unknown suffix") diff --git a/requirements-asyncio.txt b/requirements-asyncio.txt index 75a435fdf5..baa9483c33 100644 --- a/requirements-asyncio.txt +++ b/requirements-asyncio.txt @@ -1,7 +1,7 @@ -python-dateutil>=2.8.2 # BSD +python-dateutil>=3.0.0 # BSD PyYAML>=6.0.3 # MIT urllib3>=2.8.0 # MIT -aiohttp>=3.14.3,<4.0.0 # Apache-2.0 +aiohttp>=3.14.5,<4.0.0 # Apache-2.0 aiohttp-retry>=2.9.1 # MIT -pydantic>=2.13.5 # MIT +pydantic>=2.14.2 # MIT typing-extensions>=4.16.0 # PSF diff --git a/requirements.txt b/requirements.txt index b30649eed7..25341f983f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ -certifi>=2026.7.22 # MPL -python-dateutil>=2.8.2 # BSD -pydantic>=2.13.5 # MIT -setuptools>=84.0.0 # PSF/ZPL +certifi>=2024.7.4 # MPL -- oldest release without a known advisory (CVE-2024-39689) +python-dateutil>=3.0.0 # BSD +pydantic>=2.14.2 # MIT +setuptools>=84.2.1 # PSF/ZPL PyYAML>=6.0.3 # MIT websocket-client>=0.32.0,!=0.40.0,!=0.41.*,!=0.42.* # LGPLv2+ requests # Apache-2.0 requests-oauthlib # ISC +oauthlib>=4.0.0 # ISC -- CVE-2026-49264, CVE-2026-49265; remove once requests-oauthlib bumps its own lower bound typing-extensions>=4.16.0 # PSF urllib3>=2.8.0,<3.0.0 # MIT durationpy>=0.11 # MIT diff --git a/scripts/constants.py b/scripts/constants.py index a0eb82e1d0..42c8943961 100644 --- a/scripts/constants.py +++ b/scripts/constants.py @@ -18,7 +18,7 @@ KUBERNETES_BRANCH = "release-1.37" # client version for packaging and releasing. -CLIENT_VERSION = "37.0.1" +CLIENT_VERSION = "37.0.2" # Name of the release package PACKAGE_NAME = "kubernetes" diff --git a/setup-asyncio.py b/setup-asyncio.py index 04b5cf828f..d2359a13f7 100644 --- a/setup-asyncio.py +++ b/setup-asyncio.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client-asyncio.sh. -CLIENT_VERSION = "37.0.1" +CLIENT_VERSION = "37.0.2" PACKAGE_NAME = "kubernetes.aio" DEVELOPMENT_STATUS = "5 - Production/Stable" diff --git a/setup-release.py b/setup-release.py index 5d1217f897..d0ff5f5eb1 100644 --- a/setup-release.py +++ b/setup-release.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client.sh. -CLIENT_VERSION = "37.0.1" +CLIENT_VERSION = "37.0.2" PACKAGE_NAME = "kubernetes" DEVELOPMENT_STATUS = "5 - Production/Stable" diff --git a/setup.py b/setup.py index abc4f74101..6bfec0c349 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # Do not edit these constants. They will be updated automatically # by scripts/update-client.sh. -CLIENT_VERSION = "37.0.1" +CLIENT_VERSION = "37.0.2" PACKAGE_NAME = "kubernetes" DEVELOPMENT_STATUS = "5 - Production/Stable" diff --git a/test-requirements-asyncio.txt b/test-requirements-asyncio.txt index 44f8f68f0a..d8147a351b 100644 --- a/test-requirements-asyncio.txt +++ b/test-requirements-asyncio.txt @@ -1,5 +1,5 @@ isort -pluggy>=1.6.0 +pluggy>=1.7.0 py>=1.4.31 pytest pytest-cov diff --git a/test-requirements.txt b/test-requirements.txt index 0efa10cc43..a4a67cadd6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,7 +2,7 @@ coverage>=7.16.2 nose>=1.3.7 pytest pytest-cov -pluggy>=1.6.0 +pluggy>=1.7.0 randomize>=0.14 sphinx>=8.1.3 # BSD sphinx_markdown_tables