Skip to content

Commit d989d70

Browse files
Windows compatibility fixes - additional changes
1 parent 4407601 commit d989d70

File tree

10 files changed

+940
-152
lines changed

10 files changed

+940
-152
lines changed

.github/workflows/test.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,27 @@ jobs:
4848
with:
4949
fail_ci_if_error: false
5050
verbose: true
51+
52+
build_windows:
53+
runs-on: windows-latest
54+
strategy:
55+
matrix:
56+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
57+
58+
steps:
59+
- uses: actions/checkout@v4
60+
with:
61+
submodules: true
62+
- name: Set up Python ${{ matrix.python-version }}
63+
uses: actions/setup-python@v5
64+
with:
65+
python-version: ${{ matrix.python-version }}
66+
- name: Install dependencies
67+
run: |
68+
python -m pip install --upgrade pip
69+
pip install -r requirements.txt
70+
- name: Install Tox and any other packages
71+
run: pip install tox
72+
73+
- name: Test
74+
run: tox -e py # Run tox using the version of Python in `PATH`

kubernetes/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@
2222
from . import watch
2323
from . import stream
2424
from . import utils
25-
from . import leaderelection
25+
import sys
26+
27+
if sys.platform != 'win32':
28+
from . import leaderelection

kubernetes/base/config/kube_config.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,21 +423,21 @@ def _refresh_oidc(self, provider):
423423
config = Configuration()
424424

425425
if 'idp-certificate-authority-data' in provider['config']:
426-
ca_cert = tempfile.NamedTemporaryFile(delete=True)
427-
428-
if PY3:
426+
ca_cert = tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8')
427+
try:
429428
cert = base64.b64decode(
430429
provider['config']['idp-certificate-authority-data']
431430
).decode('utf-8')
432-
else:
433-
cert = base64.b64decode(
434-
provider['config']['idp-certificate-authority-data'] + "=="
435-
)
436-
437-
with open(ca_cert.name, 'w') as fh:
438-
fh.write(cert)
439-
440-
config.ssl_ca_cert = ca_cert.name
431+
ca_cert.write(cert)
432+
ca_cert.close()
433+
config.ssl_ca_cert = ca_cert.name
434+
if len(_temp_files) == 0:
435+
atexit.register(_cleanup_temp_files)
436+
_temp_files[ca_cert.name] = ca_cert.name
437+
except Exception:
438+
ca_cert.close()
439+
os.unlink(ca_cert.name)
440+
raise
441441

442442
elif 'idp-certificate-authority' in provider['config']:
443443
config.ssl_ca_cert = provider['config']['idp-certificate-authority']

kubernetes/base/watch/watch_test.py

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import json
2222

23-
from unittest.mock import Mock, call, patch, MagicMock
23+
from unittest.mock import Mock, call
2424

2525
from kubernetes import client,config
2626

@@ -131,8 +131,8 @@ def test_watch_with_multibyte_utf8(self):
131131
'
132132
# same copyright character with bytes split across two stream chunks
133133
b'{"type":"MODIFIED","object":{"data":{"utf-8":"\xC2',
134-
b'\xA9 3"},"metadata":{"n',
135-
# more chunks of the same event, sent as a mix of bytes and strings
134+
b'\xA9 3"},"metadata":{
135+
', # more chunks of the same event, sent as a mix of bytes and strings
136136
'ame":"test3","resourceVersion":"3"',
137137
'}}}
138138
',r' b'\n'
@@ -180,8 +180,8 @@ def test_watch_with_invalid_utf8(self):
180180
b'',
181181
b'3","invalid":"\xE0\x80',
182182
b'\xAF ',
183-
'3"},"metadata":{"n',
184-
'ame":"test3"',
183+
'3"},"metadata":{
184+
', 'ame":"test3"',
185185
'}}}
186186
',r' b'\n'
187187
])
@@ -592,60 +592,47 @@ def test_pod_log_empty_lines(self):
592592

593593
def test_watch_with_deserialize_param(self):
594594
"""test watch.stream() deserialize param"""
595-
595+
# prepare test data
596596
test_json = (
597597
'{"type": "ADDED", 'r'
598598
'"object": {"metadata": {"name": "test1", "resourceVersion": "1"}, 'r'
599599
'"spec": {}, "status": {}}}
600600
')
601+
fake_resp = Mock()
602+
fake_resp.close = Mock()
603+
fake_resp.release_conn = Mock()
604+
fake_resp.stream = Mock(return_value=[test_json + '\n'])
601605

602-
# Mock object for deserialize=True case
603-
metadata_mock = MagicMock()
604-
metadata_mock.name = 'test1'
605-
metadata_mock.resource_version = '1'
606-
607-
object_mock = MagicMock()
608-
object_mock.metadata = metadata_mock
609-
610-
event_deserialized = {
611-
'type': 'ADDED',
612-
'object': object_mock,
613-
'raw_object': json.loads(test_json)['object']
614-
}
615-
616-
# Event for deserialize=False case - object is plain dict
617-
event_raw = {
618-
'type': 'ADDED',
619-
'object': json.loads(test_json)['object'],
620-
'raw_object': json.loads(test_json)['object']
621-
}
622-
623-
# Patch Watch.stream to return event_deserialized for deserialize=True
624-
# and event_raw for deserialize=False - handle both calls with side_effect
625-
def stream_side_effect(func, deserialize):
626-
if deserialize:
627-
return [event_deserialized]
628-
else:
629-
return [event_raw]
630-
631-
with patch.object(Watch, 'stream', side_effect=stream_side_effect):
632-
633-
w = Watch()
606+
fake_api = Mock()
607+
fake_api.get_namespaces = Mock(return_value=fake_resp)
608+
fake_api.get_namespaces.__doc__ = ':return: V1NamespaceList'
634609

635-
# test case with deserialize=True
636-
for e in w.stream(lambda: None, deserialize=True): # dummy API func
637-
self.assertEqual("ADDED", e['type'])
638-
self.assertTrue(hasattr(e['object'], 'metadata'))
639-
self.assertEqual("test1", e['object'].metadata.name)
640-
self.assertEqual("1", e['object'].metadata.resource_version)
641-
self.assertEqual(event_deserialized['raw_object'], e['raw_object'])
642-
643-
# test case with deserialize=False
644-
for e in w.stream(lambda: None, deserialize=False):
645-
self.assertEqual("ADDED", e['type'])
646-
self.assertIsInstance(e['object'], dict)
647-
self.assertEqual("test1", e['object']['metadata']['name'])
648-
self.assertEqual("1", e['object']['metadata']['resourceVersion'])
610+
# test case with deserialize=True
611+
w = Watch()
612+
for e in w.stream(fake_api.get_namespaces, deserialize=True):
613+
self.assertEqual("ADDED", e['type'])
614+
# Verify that the object is deserialized correctly
615+
self.assertTrue(hasattr(e['object'], 'metadata'))
616+
self.assertEqual("test1", e['object'].metadata.name)
617+
self.assertEqual("1", e['object'].metadata.resource_version)
618+
# Verify that the original object is saved
619+
self.assertEqual(json.loads(test_json)['object'], e['raw_object'])
620+
621+
# test case with deserialize=False
622+
w = Watch()
623+
for e in w.stream(fake_api.get_namespaces, deserialize=False):
624+
self.assertEqual("ADDED", e['type'])
625+
# The validation object remains in the original dictionary format
626+
self.assertIsInstance(e['object'], dict)
627+
self.assertEqual("test1", e['object']['metadata']['name'])
628+
self.assertEqual("1", e['object']['metadata']['resourceVersion'])
629+
w.stop() # ensure the loop terminates
630+
631+
# verify the api is called twice
632+
fake_api.get_namespaces.assert_has_calls([
633+
call(_preload_content=False, watch=True),
634+
call(_preload_content=False, watch=True)
635+
])
649636

650637
if __name__ == '__main__':
651-
unittest.main()
638+
unittest.main()

kubernetes/config/kube_config.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -423,27 +423,21 @@ def _refresh_oidc(self, provider):
423423
config = Configuration()
424424

425425
if 'idp-certificate-authority-data' in provider['config']:
426-
ca_cert = tempfile.NamedTemporaryFile(delete=True)
427-
428-
if PY3:
426+
ca_cert = tempfile.NamedTemporaryFile(delete=False, mode='w', encoding='utf-8')
427+
try:
429428
cert = base64.b64decode(
430429
provider['config']['idp-certificate-authority-data']
431430
).decode('utf-8')
432-
else:
433-
cert = base64.b64decode(
434-
provider['config']['idp-certificate-authority-data'] + "=="
435-
)
436-
437-
with open(ca_cert.name, 'w') as fh:
438-
fh.write(cert)
439-
440-
config.ssl_ca_cert = ca_cert.name
441-
442-
elif 'idp-certificate-authority' in provider['config']:
443-
config.ssl_ca_cert = provider['config']['idp-certificate-authority']
444-
445-
else:
446-
config.verify_ssl = False
431+
ca_cert.write(cert)
432+
ca_cert.close()
433+
config.ssl_ca_cert = ca_cert.name
434+
if len(_temp_files) == 0:
435+
atexit.register(_cleanup_temp_files)
436+
_temp_files[ca_cert.name] = ca_cert.name
437+
except Exception:
438+
ca_cert.close()
439+
os.unlink(ca_cert.name)
440+
raise
447441

448442
client = ApiClient(configuration=config)
449443

0 commit comments

Comments
 (0)