diff --git a/pdm.lock b/pdm.lock index c538959..f824abc 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "all", "test", "tooling"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:eb201f3975773d778d21c252fc4261e32aa9f75da203ca6ecedb45a1cd7be0b4" +content_hash = "sha256:994141b694c96209d632413a5929ba99ad3208b7bea8cf6f51fad8eaf90ff7b8" [[metadata.targets]] requires_python = "==3.13.*" @@ -54,21 +54,19 @@ files = [ [[package]] name = "cel-python" -version = "0.2.0" -requires_python = "<4.0,>=3.8" +version = "0.3.0" +requires_python = "<4.0,>=3.9" summary = "Pure Python implementation of Google Common Expression Language" groups = ["default", "all"] dependencies = [ - "jmespath<2.0.0,>=1.0.1", - "lark<0.13.0,>=0.12.0", - "python-dateutil<3.0.0,>=2.9.0.post0", - "pyyaml<7.0.0,>=6.0.1", - "types-python-dateutil<3.0.0.0,>=2.9.0.20240316", - "types-pyyaml<7.0.0.0,>=6.0.12.20240311", + "lark<0.13,>=0.12", + "pendulum<4.0.0,>=3.1.0", + "pyyaml<7.0.0,>=6.0.2", + "types-pyyaml<7.0.0.0,>=6.0.12.20250516", ] files = [ - {file = "cel_python-0.2.0-py3-none-any.whl", hash = "sha256:478ff73def7b39d51e6982f95d937a57c2b088c491c578fe5cecdbd79f476f60"}, - {file = "cel_python-0.2.0.tar.gz", hash = "sha256:75de72a5cf223ec690b236f0cc24da267219e667bd3e7f8f4f20595fcc1c0c0f"}, + {file = "cel_python-0.3.0-py3-none-any.whl", hash = "sha256:7db7460f1deed505146f87b94253af80422090ae2a18ea74abca41ff09a9edae"}, + {file = "cel_python-0.3.0.tar.gz", hash = "sha256:0fb25e0bdf68e738621f1438ecb53f2d2f4e06a48591d6844febe7a1a86e4bfe"}, ] [[package]] @@ -315,52 +313,41 @@ files = [ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] -[[package]] -name = "jmespath" -version = "1.0.1" -requires_python = ">=3.7" -summary = "JSON Matching Expressions" -groups = ["default", "all"] -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - [[package]] name = "koreo-core" -version = "0.1.11" +version = "0.1.13" requires_python = ">=3.13" summary = "Type-safe and testable KRM Templates and Workflows." groups = ["default", "all"] dependencies = [ "PyYAML==6.0.2", - "cel-python==0.2.0", + "cel-python==0.3.0", "fastjsonschema==2.21.1", - "kr8s==0.20.6", + "kr8s==0.20.7", ] files = [ - {file = "koreo_core-0.1.11-py3-none-any.whl", hash = "sha256:f866e71ac58dd08d97df4e9c5cda2b23d829478da36619430ad1567ccf9482ff"}, - {file = "koreo_core-0.1.11.tar.gz", hash = "sha256:a2e37104b51f54fd404a38920d496de91cc8541216f319ee698e8a2558d31666"}, + {file = "koreo_core-0.1.13-py3-none-any.whl", hash = "sha256:3f809dc89c7cc0e9aed4cf726d7dd46499f1e3a49821241270e546e420ad7339"}, + {file = "koreo_core-0.1.13.tar.gz", hash = "sha256:7ececc1d94d34271ec7f10ecd329945d7b62761bce1e5a9714cfd1de46e7db9d"}, ] [[package]] name = "koreo-core" -version = "0.1.11" +version = "0.1.13" extras = ["test", "tooling"] requires_python = ">=3.13" summary = "Type-safe and testable KRM Templates and Workflows." groups = ["all"] dependencies = [ - "koreo-core==0.1.11", + "koreo-core==0.1.13", ] files = [ - {file = "koreo_core-0.1.11-py3-none-any.whl", hash = "sha256:f866e71ac58dd08d97df4e9c5cda2b23d829478da36619430ad1567ccf9482ff"}, - {file = "koreo_core-0.1.11.tar.gz", hash = "sha256:a2e37104b51f54fd404a38920d496de91cc8541216f319ee698e8a2558d31666"}, + {file = "koreo_core-0.1.13-py3-none-any.whl", hash = "sha256:3f809dc89c7cc0e9aed4cf726d7dd46499f1e3a49821241270e546e420ad7339"}, + {file = "koreo_core-0.1.13.tar.gz", hash = "sha256:7ececc1d94d34271ec7f10ecd329945d7b62761bce1e5a9714cfd1de46e7db9d"}, ] [[package]] name = "kr8s" -version = "0.20.6" +version = "0.20.7" requires_python = ">=3.9" summary = "A Kubernetes API library" groups = ["default", "all"] @@ -377,8 +364,8 @@ dependencies = [ "typing-extensions>=4.12.2", ] files = [ - {file = "kr8s-0.20.6-py3-none-any.whl", hash = "sha256:7051808ea3cfb3d407a9485ac1ef7f228e798ff0ac6fba2399bedcda5a9a67f9"}, - {file = "kr8s-0.20.6.tar.gz", hash = "sha256:49b017001d7f8d2c2aa5c0b88bd277d97e5ef28f5601c11dee1148d8bf669dbf"}, + {file = "kr8s-0.20.7-py3-none-any.whl", hash = "sha256:e489b97ff513c167f427f479ad5420c78adffd1a6ce5033b079109374200c0c6"}, + {file = "kr8s-0.20.7.tar.gz", hash = "sha256:ac45e966beea0f6f92f635b3e61e64b8e27962b4825d77b814a663e819a8ec16"}, ] [[package]] @@ -413,6 +400,31 @@ files = [ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "pendulum" +version = "3.1.0" +requires_python = ">=3.9" +summary = "Python datetimes made easy" +groups = ["default", "all"] +dependencies = [ + "python-dateutil>=2.6", + "tzdata>=2020.1", +] +files = [ + {file = "pendulum-3.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:28658b0baf4b30eb31d096a375983cfed033e60c0a7bbe94fa23f06cd779b50b"}, + {file = "pendulum-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b114dcb99ce511cb8f5495c7b6f0056b2c3dba444ef1ea6e48030d7371bd531a"}, + {file = "pendulum-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2404a6a54c80252ea393291f0b7f35525a61abae3d795407f34e118a8f133a18"}, + {file = "pendulum-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d06999790d9ee9962a1627e469f98568bf7ad1085553fa3c30ed08b3944a14d7"}, + {file = "pendulum-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94751c52f6b7c306734d1044c2c6067a474237e1e5afa2f665d1fbcbbbcf24b3"}, + {file = "pendulum-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5553ac27be05e997ec26d7f004cf72788f4ce11fe60bb80dda604a64055b29d0"}, + {file = "pendulum-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f8dee234ca6142bf0514368d01a72945a44685aaa2fc4c14c98d09da9437b620"}, + {file = "pendulum-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7378084fe54faab4ee481897a00b710876f2e901ded6221671e827a253e643f2"}, + {file = "pendulum-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8539db7ae2c8da430ac2515079e288948c8ebf7eb1edd3e8281b5cdf433040d6"}, + {file = "pendulum-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:1ce26a608e1f7387cd393fba2a129507c4900958d4f47b90757ec17656856571"}, + {file = "pendulum-3.1.0-py3-none-any.whl", hash = "sha256:f9178c2a8e291758ade1e8dd6371b1d26d08371b4c7730a6e9a3ef8b16ebae0f"}, + {file = "pendulum-3.1.0.tar.gz", hash = "sha256:66f96303560f41d097bee7d2dc98ffca716fbb3a832c4b3062034c2d45865015"}, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -436,9 +448,20 @@ files = [ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +[[package]] +name = "pygments" +version = "2.19.1" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["test"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + [[package]] name = "pyright" -version = "1.1.397" +version = "1.1.401" requires_python = ">=3.7" summary = "Command line wrapper for pyright" groups = ["tooling"] @@ -447,32 +470,33 @@ dependencies = [ "typing-extensions>=4.1", ] files = [ - {file = "pyright-1.1.397-py3-none-any.whl", hash = "sha256:2e93fba776e714a82b085d68f8345b01f91ba43e1ab9d513e79b70fc85906257"}, - {file = "pyright-1.1.397.tar.gz", hash = "sha256:07530fd65a449e4b0b28dceef14be0d8e0995a7a5b1bb2f3f897c3e548451ce3"}, + {file = "pyright-1.1.401-py3-none-any.whl", hash = "sha256:6fde30492ba5b0d7667c16ecaf6c699fab8d7a1263f6a18549e0b00bf7724c06"}, + {file = "pyright-1.1.401.tar.gz", hash = "sha256:788a82b6611fa5e34a326a921d86d898768cddf59edde8e93e56087d277cc6f1"}, ] [[package]] name = "pytest" -version = "8.3.5" -requires_python = ">=3.8" +version = "8.4.0" +requires_python = ">=3.9" summary = "pytest: simple powerful testing with Python" groups = ["test"] dependencies = [ - "colorama; sys_platform == \"win32\"", - "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", - "iniconfig", - "packaging", + "colorama>=0.4; sys_platform == \"win32\"", + "exceptiongroup>=1; python_version < \"3.11\"", + "iniconfig>=1", + "packaging>=20", "pluggy<2,>=1.5", + "pygments>=2.7.2", "tomli>=1; python_version < \"3.11\"", ] files = [ - {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, - {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, + {file = "pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e"}, + {file = "pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6"}, ] [[package]] name = "pytest-cov" -version = "6.0.0" +version = "6.1.1" requires_python = ">=3.9" summary = "Pytest plugin for measuring coverage." groups = ["test"] @@ -481,8 +505,8 @@ dependencies = [ "pytest>=4.6", ] files = [ - {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, - {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, + {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, + {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, ] [[package]] @@ -545,29 +569,29 @@ files = [ [[package]] name = "ruff" -version = "0.11.2" +version = "0.11.13" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["tooling"] files = [ - {file = "ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477"}, - {file = "ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272"}, - {file = "ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147"}, - {file = "ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b"}, - {file = "ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9"}, - {file = "ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab"}, - {file = "ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630"}, - {file = "ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f"}, - {file = "ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc"}, - {file = "ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080"}, - {file = "ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4"}, - {file = "ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94"}, + {file = "ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46"}, + {file = "ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48"}, + {file = "ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71"}, + {file = "ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432"}, + {file = "ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492"}, + {file = "ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250"}, + {file = "ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3"}, + {file = "ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b"}, + {file = "ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514"}, ] [[package]] @@ -592,26 +616,15 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20241206" -requires_python = ">=3.8" -summary = "Typing stubs for python-dateutil" -groups = ["default", "all"] -files = [ - {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, - {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, -] - [[package]] name = "types-pyyaml" -version = "6.0.12.20250402" +version = "6.0.12.20250516" requires_python = ">=3.9" summary = "Typing stubs for PyYAML" groups = ["default", "all"] files = [ - {file = "types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681"}, - {file = "types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075"}, + {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, + {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, ] [[package]] @@ -625,6 +638,17 @@ files = [ {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] +[[package]] +name = "tzdata" +version = "2025.2" +requires_python = ">=2" +summary = "Provider of IANA time zone data" +groups = ["default", "all"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + [[package]] name = "uvloop" version = "0.21.0" diff --git a/pyproject.toml b/pyproject.toml index 3dacb51..b108c6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,15 @@ [project] name = "koreo-controller" -version = "0.1.16" +version = "0.1.17" description = "Koreo Controller runs Koreo Core as a Kubernetes Controller." authors = [ {name = "Eric Larssen", email = "eric.larssen@realkinetic.com"}, {name = "Robert Kluin", email = "robert.kluin@realkinetic.com"}, ] dependencies = [ - "koreo-core==0.1.11", - "cel-python==0.2.0", - "kr8s==0.20.6", + "koreo-core==0.1.13", + "cel-python==0.3.0", + "kr8s==0.20.7", "uvloop==0.21.0", ] requires-python = "==3.13.*" @@ -21,12 +21,12 @@ distribution = false [dependency-groups] test = [ - "pytest==8.3.5", - "pytest-cov==6.0.0", + "pytest==8.4.0", + "pytest-cov==6.1.1", ] tooling = [ - "ruff==0.11.2", - "pyright==1.1.397", + "ruff==0.11.13", + "pyright==1.1.401", ] all = ["koreo-core[test,tooling]"] diff --git a/src/controller/custom_workflow.py b/src/controller/custom_workflow.py index 09badf9..d387311 100644 --- a/src/controller/custom_workflow.py +++ b/src/controller/custom_workflow.py @@ -50,7 +50,12 @@ async def workflow_controller_system( event_handler, request_queue = reconcile.get_event_handler(namespace=namespace) event_config = events.Configuration( - event_handler=event_handler, namespace=namespace + event_handler=event_handler, + namespace=namespace, + max_unknown_errors=10, + retry_delay_base=30, + retry_delay_jitter=30, + retry_delay_max=900, ) if not ( @@ -95,7 +100,11 @@ async def workflow_controller_system( managed_resource_api.timeout = RECONNECT_TIMEOUT scheduler_config = scheduler.Configuration( - work_processor=_configure_reconciler(api=managed_resource_api) + concurrency=2, + frequency_seconds=1200, + retry_delay_base=30, + retry_delay_max=900, + work_processor=_configure_reconciler(api=managed_resource_api), ) async with asyncio.TaskGroup() as tg: diff --git a/src/controller/events.py b/src/controller/events.py index 56c8ffc..602cb8d 100644 --- a/src/controller/events.py +++ b/src/controller/events.py @@ -46,9 +46,9 @@ class Configuration(NamedTuple): max_unknown_errors: int = 10 - retry_delay_base: int = 10 - retry_delay_jitter: int = 15 - retry_delay_max: int = 300 + retry_delay_base: int = 30 + retry_delay_jitter: int = 30 + retry_delay_max: int = 900 def _watch_key(api_version: str, kind: str): diff --git a/src/controller/koreo_cache.py b/src/controller/koreo_cache.py index d5cee9c..967b8f4 100644 --- a/src/controller/koreo_cache.py +++ b/src/controller/koreo_cache.py @@ -10,9 +10,9 @@ MAX_SYS_ERRORS = 10 -RETRY_MAX_DELAY = 300 -RETRY_BASE_DELAY = 10 -RETRY_JITTER = 5 +RETRY_MAX_DELAY = 900 +RETRY_BASE_DELAY = 30 +RETRY_JITTER = 30 async def load_cache( @@ -53,7 +53,7 @@ async def load_cache( spec=resource.raw.get("spec", {}), ) - logger.debug(f"Initial {plural_kind}.{api_version} cache load complete.") + logger.info(f"Initial {plural_kind}.{api_version} cache load complete.") async def maintain_cache( @@ -120,21 +120,28 @@ async def maintain_cache( ) error_retries = 0 except BaseException as err: - logger.exception( - f"Restarting {plural_kind}.{api_version} cache maintainer watch " - f"due to error: {err}" - ) - error_retries += 1 if error_retries > MAX_SYS_ERRORS: - logger.error(f"Retry limit ({MAX_SYS_ERRORS}) exceeded.") + logger.error( + f"Retry limit ({MAX_SYS_ERRORS}) exceeded for" + f"{plural_kind}.{api_version} cache maintainer watch" + ) + raise - # NOTE: This is just to prevent completely blowing up the API - # Server if there's an issue. It probably should have a back-off - # based on the last retry time. - await asyncio.sleep( + sleep_time = ( min((2**error_retries) * RETRY_BASE_DELAY, RETRY_MAX_DELAY) + random.random() * RETRY_JITTER ) + + logger.exception( + f"Waiting {sleep_time} seconds before restarting " + f"{plural_kind}.{api_version} cache maintainer watch " + f"due to error: {err}" + ) + + # NOTE: This is just to prevent completely blowing up the API + # Server if there's an issue. It probably should have a back-off + # based on the last retry time. + await asyncio.sleep(sleep_time) diff --git a/src/koreo_controller.py b/src/koreo_controller.py index 4332ef6..420273d 100644 --- a/src/koreo_controller.py +++ b/src/koreo_controller.py @@ -1,9 +1,9 @@ +from typing import Awaitable +import asyncio import json import logging import os -import asyncio - class JsonFormatter(logging.Formatter): def format(self, record): @@ -31,8 +31,6 @@ def format(self, record): logging.getLogger(name="kr8s._api").setLevel(lib_log_level) logging.getLogger(name="kr8s._auth").setLevel(lib_log_level) -import os - import uvloop import kr8s.asyncio @@ -64,13 +62,6 @@ def format(self, record): RESOURCE_NAMESPACE = os.environ.get("RESOURCE_NAMESPACE", "koreo-testing") -EXIT_CONTROLLER_ERROR = 1 -EXIT_CONTROLLER_UNEXPECTED_RETURN = 2 - -EXIT_CACHER_ERROR = 5 -EXIT_CACHER_UNEXPECTED_RETURN = 6 - - # NOTE: These are ordered so that each group's dependencies will already be # loaded when initially loaded into cache. KOREO_RESOURCES = [ @@ -92,65 +83,47 @@ async def _koreo_resource_cache_manager( kind_title: str, resource_class: type, preparer, + shutdown_trigger: asyncio.Event, ): - # Block until completion. - await koreo_cache.load_cache( - api=api, - namespace=namespace, - api_version=API_VERSION, - plural_kind=f"{kind_title.lower()}s", - kind_title=kind_title, - resource_class=resource_class, - preparer=preparer, - ) - - if not HOT_LOADING: - return - - # Spawns long-term (infinite) cache maintainer in background - await koreo_cache.maintain_cache( - api=api, - namespace=namespace, - api_version=API_VERSION, - plural_kind=f"{kind_title.lower()}s", - kind_title=kind_title, - resource_class=resource_class, - preparer=preparer, - reconnect_timeout=RECONNECT_TIMEOUT, - ) - - -def _cache_task_complete(cache_task: asyncio.Task): - if cache_task.cancelled(): - logger.info(f"Cache task ({cache_task.get_name()}) quit due to cancel.") - return - - if cache_task.exception(): - logger.error( - f"Cache task ({cache_task.get_name()}) quit due to error: {cache_task.exception()}." + """ + These are long-term (infinite) cache maintainers that will run in the + background to watch for updates to Koreo Resources. + """ + try: + await koreo_cache.maintain_cache( + api=api, + namespace=namespace, + api_version=API_VERSION, + plural_kind=f"{kind_title.lower()}s", + kind_title=kind_title, + resource_class=resource_class, + preparer=preparer, + reconnect_timeout=RECONNECT_TIMEOUT, ) - raise SystemExit(EXIT_CACHER_ERROR) + finally: + shutdown_trigger.set() - if not HOT_LOADING: - return - logger.error(f"Cache task ({cache_task.get_name()}) quit due to unexpected return.") - raise SystemExit(EXIT_CACHER_UNEXPECTED_RETURN) +async def _controller_engine_wrapper( + shutdown_trigger: asyncio.Event, controller: Awaitable +): + try: + await controller + except KeyboardInterrupt: + logger.debug(f"Controller engine quit due to user quit.") + raise -def _controller_engine_complete(controller_task: asyncio.Task): - if controller_task.cancelled(): - logger.info("Controller engine quit due to cancel.") - return + except asyncio.CancelledError: + logger.info(f"Controller engine quit due to cancel.") + raise - if controller_task.exception(): - logger.error( - f"Controller engine quit due to error: {controller_task.exception()}." - ) - raise SystemExit(EXIT_CONTROLLER_ERROR) + except BaseException as err: + logger.error(f"Controller engine quit due to error: {err}.") + raise - logger.error(f"Controller engine quit due to unexpected return.") - raise SystemExit(EXIT_CONTROLLER_UNEXPECTED_RETURN) + finally: + shutdown_trigger.set() async def main(): @@ -170,34 +143,113 @@ async def main(): (KOREO_NAMESPACE, "Workflow", Workflow, prepare_workflow), ) - async with asyncio.TaskGroup() as main_tg: - for namespace, kind_title, resource_class, preparer in KOREO_RESOURCES: - cache_task = main_tg.create_task( - _koreo_resource_cache_manager( - api=api, - namespace=namespace, - kind_title=kind_title, - resource_class=resource_class, - preparer=preparer, - ), - name=f"cache-maintainer-{kind_title.lower()}", + for namespace, kind_title, resource_class, preparer in KOREO_RESOURCES: + try: + # Load the Koreo resources sequentially, for efficiency purposes. + await koreo_cache.load_cache( + api=api, + namespace=namespace, + api_version=API_VERSION, + plural_kind=f"{kind_title.lower()}s", + kind_title=kind_title, + resource_class=resource_class, + preparer=preparer, + ) + + # There is a trailing return + continue + + except KeyboardInterrupt: + logger.info( + f"Initiating shutdown due to user-request. (Koreo {kind_title} Resource Load)" ) - cache_task.add_done_callback(_cache_task_complete) + + except asyncio.CancelledError: + logger.info( + f"Initiating shutdown due to cancel. (Koreo {kind_title} Resource Load)" + ) + + except BaseException as err: + logger.error( + f"Initiating shutdown due to error {err}. (Koreo {kind_title} Resource Load)" + ) + + except: + logger.critical( + f"Initiating shutdown due to non-error exception. (Koreo {kind_title} Resource Load)" + ) + + # This means the continue was not hit + return + + async with asyncio.TaskGroup() as main_tg: + shutdown_trigger = asyncio.Event() + + tasks: list[asyncio.Task] = [] + + if HOT_LOADING: + logger.info("Hot-loading Koreo Resource enabled") + for namespace, kind_title, resource_class, preparer in KOREO_RESOURCES: + tasks.append( + main_tg.create_task( + _koreo_resource_cache_manager( + api=api, + namespace=namespace, + kind_title=kind_title, + resource_class=resource_class, + preparer=preparer, + shutdown_trigger=shutdown_trigger, + ), + name=f"cache-maintainer-{kind_title.lower()}", + ) + ) # This is the schedule watcher / dispatcher for workflow crdRefs. - orchestrator_task = asyncio.create_task( - workflow_controller_system( - api=api, - namespace=RESOURCE_NAMESPACE, - workflow_updates_queue=workflow_updates_queue, + tasks.append( + asyncio.create_task( + _controller_engine_wrapper( + shutdown_trigger=shutdown_trigger, + controller=workflow_controller_system( + api=api, + namespace=RESOURCE_NAMESPACE, + workflow_updates_queue=workflow_updates_queue, + ), + ), + name="workflow-controller", ) ) - orchestrator_task.add_done_callback(_controller_engine_complete) + + try: + await shutdown_trigger.wait() + + except KeyboardInterrupt: + logger.info("Initiating shutdown due to user-request.") + + except asyncio.CancelledError: + logger.info("Initiating shutdown due to cancel.") + + except BaseException as err: + logger.error(f"Initiating shutdown due to error {err}.") + + except: + logger.critical(f"Initiating shutdown due to non-error exception.") + + logger.info("Shutting down workers") + for task in tasks: + task_name = task.get_name() + + if not (task.done() or task.cancelling()): + task.cancel("System shutdown") + logger.info(f"Stopping {task_name}") + + logger.info("Shutdown") if __name__ == "__main__": asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) try: uvloop.run(main()) - except (asyncio.CancelledError, KeyboardInterrupt): + except KeyboardInterrupt: exit(0) + + exit(1)