diff --git a/microservices/gatewayJobScheduler/app.py b/microservices/gatewayJobScheduler/app.py index c952feb3..e171df5a 100644 --- a/microservices/gatewayJobScheduler/app.py +++ b/microservices/gatewayJobScheduler/app.py @@ -4,6 +4,7 @@ from clients.namespace import NamespaceService from cryptography import x509 from cryptography.hazmat.backends import default_backend +from clients.keycloak import admin_api logger = logging.getLogger(__name__) @@ -130,4 +131,25 @@ def transform_data_by_ns(routes, certs, cert_snis): return ns_dict except Exception as err: traceback.print_exc() - logger.error("Error transforming data. %s" % str(err)) \ No newline at end of file + logger.error("Error transforming data. %s" % str(err)) + +def get_namespaces_with_perm_data_plane(perm_data_plane_value): + """ + Fetch namespaces from Keycloak group 'ns' with attribute perm-data-plane matching the given value + """ + kc = admin_api() + namespaces = [] + # Find the 'ns' group + ns_groups = kc.get_groups() + ns_group = next((g for g in ns_groups if g['name'] == 'ns'), None) + if not ns_group: + return namespaces + + # Get subgroups (namespaces) + subgroups = kc.get_group(ns_group['id']).get('subGroups', []) + for subgroup in subgroups: + attrs = subgroup.get('attributes', {}) + perm_data_plane = attrs.get('perm-data-plane', []) + if perm_data_plane_value in perm_data_plane: + namespaces.append(subgroup['name']) + return namespaces \ No newline at end of file diff --git a/microservices/gatewayJobScheduler/main.py b/microservices/gatewayJobScheduler/main.py index 7af23eb6..e3006d50 100644 --- a/microservices/gatewayJobScheduler/main.py +++ b/microservices/gatewayJobScheduler/main.py @@ -3,7 +3,7 @@ from sys import exc_info import logging import traceback -from app import transform_data_by_ns +from app import transform_data_by_ns, get_namespaces_with_perm_data_plane from clients.kong import get_records import traceback import schedule @@ -33,7 +33,17 @@ def sync_routes(): clear('sync-routes') exit(1) + # Get Gold namespaces from Keycloak + perm_data_plane_value = os.getenv('DATA_PLANE') + namespaces = get_namespaces_with_perm_data_plane(perm_data_plane_value) + data = transform_data_by_ns(routes, certs, cert_snis) + + # Add missing namespaces with no routes + for ns in namespaces: + if ns not in data: + data[ns] = [] + for ns in data: url = os.getenv('KUBE_API_URL') + '/namespaces/%s/routes/sync' % ns response = requests.post(url, headers=headers, json=data[ns], auth=( diff --git a/microservices/gatewayJobScheduler/poetry.lock b/microservices/gatewayJobScheduler/poetry.lock index 22a75e26..19b6e57e 100644 --- a/microservices/gatewayJobScheduler/poetry.lock +++ b/microservices/gatewayJobScheduler/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "autopep8" @@ -6,6 +6,7 @@ version = "1.7.0" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "autopep8-1.7.0-py2.py3-none-any.whl", hash = "sha256:6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087"}, {file = "autopep8-1.7.0.tar.gz", hash = "sha256:ca9b1a83e53a7fad65d731dc7a2a2d50aa48f43850407c59f6a1a306c4201142"}, @@ -21,6 +22,7 @@ version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, @@ -32,6 +34,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -111,6 +115,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -210,6 +215,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -221,6 +228,7 @@ version = "7.5.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, @@ -280,7 +288,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -288,6 +296,7 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -337,6 +346,7 @@ version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" +groups = ["main"] files = [ {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, @@ -355,6 +365,8 @@ version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, @@ -369,6 +381,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -380,6 +393,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -391,6 +405,7 @@ version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, @@ -402,6 +417,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -417,6 +433,7 @@ version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, @@ -428,6 +445,7 @@ version = "2.11.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, @@ -439,6 +457,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -450,6 +470,7 @@ version = "8.2.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, @@ -472,6 +493,7 @@ version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, @@ -490,6 +512,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -507,6 +530,7 @@ version = "0.19.2" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"}, {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"}, @@ -521,6 +545,7 @@ version = "3.3.0" description = "JOSE implementation in Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, @@ -542,6 +567,7 @@ version = "0.26.1" description = "python-keycloak is a Python package providing access to the Keycloak API." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-keycloak-0.26.1.tar.gz", hash = "sha256:3cd6792e18887f653c3bf055727a81059600b8ce02e975492d6b42f63ca45064"}, ] @@ -556,6 +582,7 @@ version = "2.32.1" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.1-py3-none-any.whl", hash = "sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5"}, {file = "requests-2.32.1.tar.gz", hash = "sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685"}, @@ -577,6 +604,7 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.6,<4" +groups = ["main"] files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -591,6 +619,7 @@ version = "1.2.1" description = "Job scheduling for humans." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "schedule-1.2.1-py2.py3-none-any.whl", hash = "sha256:14cdeb083a596aa1de6dc77639a1b2ac8bf6eaafa82b1c9279d3612823063d01"}, {file = "schedule-1.2.1.tar.gz", hash = "sha256:843bc0538b99c93f02b8b50e3e39886c06f2d003b24f48e1aa4cadfa3f341279"}, @@ -602,6 +631,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -613,6 +643,7 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -624,6 +655,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -635,18 +668,19 @@ version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.8" -content-hash = "5e93dcdae46feceba0aaddb39f11f810940d5bdf507697309898f7fe75057ed3" +content-hash = "51b6bf886a3c26536a2d9475f0e4fce7d4163d772b808c5d087e4c2be11aa1ed" diff --git a/microservices/gatewayJobScheduler/pyproject.toml b/microservices/gatewayJobScheduler/pyproject.toml index 28fcb756..51bd27fb 100644 --- a/microservices/gatewayJobScheduler/pyproject.toml +++ b/microservices/gatewayJobScheduler/pyproject.toml @@ -18,6 +18,7 @@ python-dotenv = "^0.19.1" pytest = "^8.2.0" pytest-cov = "^5.0.0" pytest-mock = "^3.14.0" +coverage = "<8.0" [tool.coverage.run] relative_files = true diff --git a/microservices/gatewayJobScheduler/tests/conftest.py b/microservices/gatewayJobScheduler/tests/conftest.py index e9be8d6b..042b358b 100644 --- a/microservices/gatewayJobScheduler/tests/conftest.py +++ b/microservices/gatewayJobScheduler/tests/conftest.py @@ -1,6 +1,9 @@ import pytest import logging import logging.config +import os +import sys +from unittest import mock logging.config.dictConfig({ 'version': 1, @@ -30,3 +33,29 @@ # Sample certificate in PEM format for testing SAMPLE_CERT = "-----BEGIN CERTIFICATE-----\nMIID0DCCArigAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJGUjET\nMBEGA1UECAwKU29tZS1TdGF0ZTEOMAwGA1UEBwwFUGFyaXMxDTALBgNVBAoMBERp\nbWkxDTALBgNVBAsMBE5TQlUxEDAOBgNVBAMMB0RpbWkgQ0ExGzAZBgkqhkiG9w0B\nCQEWDGRpbWlAZGltaS5mcjAeFw0xNDAxMjgyMDM2NTVaFw0yNDAxMjYyMDM2NTVa\nMFsxCzAJBgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJ\nbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFDASBgNVBAMMC3d3dy5kaW1pLmZyMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpnaPKLIKdvx98KW68lz8pGa\nRRcYersNGqPjpifMVjjE8LuCoXgPU0HePnNTUjpShBnynKCvrtWhN+haKbSp+QWX\nSxiTrW99HBfAl1MDQyWcukoEb9Cw6INctVUN4iRvkn9T8E6q174RbcnwA/7yTc7p\n1NCvw+6B/aAN9l1G2pQXgRdYC/+G6o1IZEHtWhqzE97nY5QKNuUVD0V09dc5CDYB\naKjqetwwv6DFk/GRdOSEd/6bW+20z0qSHpa3YNW6qSp+x5pyYmDrzRIR03os6Dau\nZkChSRyc/Whvurx6o85D6qpzywo8xwNaLZHxTQPgcIA5su9ZIytv9LH2E+lSwwID\nAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy\nYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU+tugFtyN+cXe1wxUqeA7X+yS3bgw\nHwYDVR0jBBgwFoAUhMwqkbBrGp87HxfvwgPnlGgVR64wDQYJKoZIhvcNAQEFBQAD\nggEBAIEEmqqhEzeXZ4CKhE5UM9vCKzkj5Iv9TFs/a9CcQuepzplt7YVmevBFNOc0\n+1ZyR4tXgi4+5MHGzhYCIVvHo4hKqYm+J+o5mwQInf1qoAHuO7CLD3WNa1sKcVUV\nvepIxc/1aHZrG+dPeEHt0MdFfOw13YdUc2FH6AqEdcEL4aV5PXq2eYR8hR4zKbc1\nfBtuqUsvA8NWSIyzQ16fyGve+ANf6vXvUizyvwDrPRv/kfvLNa3ZPnLMMxU98Mvh\nPXy3PkB8++6U4Y3vdk2Ni2WYYlIls8yqbM4327IKmkDc2TimS8u60CT47mKU7aDY\ncbTV5RDkrlaYwm5yqlTIglvCv7o=\n-----END CERTIFICATE-----" SAMPLE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAvpnaPKLIKdvx98KW68lz8pGaRRcYersNGqPjpifMVjjE8LuC\noXgPU0HePnNTUjpShBnynKCvrtWhN+haKbSp+QWXSxiTrW99HBfAl1MDQyWcukoE\nb9Cw6INctVUN4iRvkn9T8E6q174RbcnwA/7yTc7p1NCvw+6B/aAN9l1G2pQXgRdY\nC/+G6o1IZEHtWhqzE97nY5QKNuUVD0V09dc5CDYBaKjqetwwv6DFk/GRdOSEd/6b\nW+20z0qSHpa3YNW6qSp+x5pyYmDrzRIR03os6DauZkChSRyc/Whvurx6o85D6qpz\nywo8xwNaLZHxTQPgcIA5su9ZIytv9LH2E+lSwwIDAQABAoIBAFml8cD9a5pMqlW3\nf9btTQz1sRL4Fvp7CmHSXhvjsjeHwhHckEe0ObkWTRsgkTsm1XLu5W8IITnhn0+1\niNr+78eB+rRGngdAXh8diOdkEy+8/Cee8tFI3jyutKdRlxMbwiKsouVviumoq3fx\nOGQYwQ0Z2l/PvCwy/Y82ffq3ysC5gAJsbBYsCrg14bQo44ulrELe4SDWs5HCjKYb\nEI2b8cOMucqZSOtxg9niLN/je2bo/I2HGSawibgcOdBms8k6TvsSrZMr3kJ5O6J+\n77LGwKH37brVgbVYvbq6nWPL0xLG7dUv+7LWEo5qQaPy6aXb/zbckqLqu6/EjOVe\nydG5JQECgYEA9kKfTZD/WEVAreA0dzfeJRu8vlnwoagL7cJaoDxqXos4mcr5mPDT\nkbWgFkLFFH/AyUnPBlK6BcJp1XK67B13ETUa3i9Q5t1WuZEobiKKBLFm9DDQJt43\nuKZWJxBKFGSvFrYPtGZst719mZVcPct2CzPjEgN3Hlpt6fyw3eOrnoECgYEAxiOu\njwXCOmuGaB7+OW2tR0PGEzbvVlEGdkAJ6TC/HoKM1A8r2u4hLTEJJCrLLTfw++4I\nddHE2dLeR4Q7O58SfLphwgPmLDezN7WRLGr7Vyfuv7VmaHjGuC3Gv9agnhWDlA2Q\ngBG9/R9oVfL0Dc7CgJgLeUtItCYC31bGT3yhV0MCgYEA4k3DG4L+RN4PXDpHvK9I\npA1jXAJHEifeHnaW1d3vWkbSkvJmgVf+9U5VeV+OwRHN1qzPZV4suRI6M/8lK8rA\nGr4UnM4aqK4K/qkY4G05LKrik9Ev2CgqSLQDRA7CJQ+Jn3Nb50qg6hFnFPafN+J7\t\t\n7juWln08wFYV4Atpdd+9XQECgYBxizkZFL+9IqkfOcONvWAzGo+Dq1N0L3J4iTIk\nw56CKWXyj88d4qB4eUU3yJ4uB4S9miaW/eLEwKZIbWpUPFAn0db7i6h3ZmP5ZL8Q\nqS3nQCb9DULmU2/tU641eRUKAmIoka1g9sndKAZuWo+o6fdkIb1RgObk9XNn8R4r\npsv+aQKBgB+CIcExR30vycv5bnZN9EFlIXNKaeMJUrYCXcRQNvrnUIUBvAO8+jAe\nCdLygS5RtgOLZib0IVErqWsP3EI1ACGuLts0vQ9GFLQGaN1SaMS40C9kvns1mlDu\nLhIhYpJ8UsCVt5snWo2N+M+6ANh5tpWdQnEK6zILh4tRbuzaiHgb\n-----END RSA PRIVATE KEY-----" + +os.environ.update({ + 'KC_SERVER_URL': 'https://test-keycloak-server', + 'KC_REALM': 'test-realm', + 'KC_CLIENT_ID': 'test-client', + 'KC_USERNAME': 'test-admin', + 'KC_PASSWORD': 'test-password', + 'KC_USER_REALM': 'test-realm' +}) + +mock_keycloak_admin = mock.Mock() +sys.modules['keycloak'] = mock.Mock() +sys.modules['keycloak'].KeycloakAdmin = mock.Mock(return_value=mock_keycloak_admin) + +@pytest.fixture(autouse=True) +def mock_keycloak_clients(): + """Provide mock Keycloak client for all tests""" + yield { + 'admin_client': mock_keycloak_admin + } + +@pytest.fixture(autouse=True) +def reset_keycloak_mock(): + """Reset mock state between tests""" + mock_keycloak_admin.reset_mock() + yield diff --git a/microservices/gatewayJobScheduler/tests/pytest.ini b/microservices/gatewayJobScheduler/tests/pytest.ini new file mode 100644 index 00000000..c8c13343 --- /dev/null +++ b/microservices/gatewayJobScheduler/tests/pytest.ini @@ -0,0 +1,16 @@ +[pytest] +addopts = -v +testpaths = tests +python_files = test_*.py +log_cli_level = INFO +log_cli_format = %(asctime)s %(levelname)s %(message)s +log_file_level = ERROR +log_file_format = %(asctime)s %(levelname)s %(message)s +filterwarnings = ignore::DeprecationWarning +env = + KC_SERVER_URL=https://test-keycloak-server + KC_REALM=test-realm + KC_CLIENT_ID=test-client + KC_USERNAME=test-admin + KC_PASSWORD=test-password + KC_USER_REALM=test-realm diff --git a/microservices/gatewayJobScheduler/tests/test_get_namespaces_with_perm_data_plane.py b/microservices/gatewayJobScheduler/tests/test_get_namespaces_with_perm_data_plane.py new file mode 100644 index 00000000..2824a97d --- /dev/null +++ b/microservices/gatewayJobScheduler/tests/test_get_namespaces_with_perm_data_plane.py @@ -0,0 +1,158 @@ +import pytest +from app import get_namespaces_with_perm_data_plane + +def test_get_namespaces_with_perm_data_plane_happy_path(mock_keycloak_clients): + """Test successful retrieval of namespaces with matching perm-data-plane""" + mock_kc = mock_keycloak_clients['admin_client'] + + # Mock groups response + mock_kc.get_groups.return_value = [ + {'id': 'group1', 'name': 'other-group'}, + {'id': 'ns-group-id', 'name': 'ns'} + ] + + # Mock subgroups with various perm-data-plane values + mock_kc.get_group.return_value = { + 'subGroups': [ + { + 'name': 'namespace1', + 'attributes': { + 'perm-data-plane': ['test-dp', 'other-dp'] + } + }, + { + 'name': 'namespace2', + 'attributes': { + 'perm-data-plane': ['test-dp'] + } + }, + { + 'name': 'namespace3', + 'attributes': { + 'perm-data-plane': ['different-dp'] + } + } + ] + } + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == ['namespace1', 'namespace2'] + mock_kc.get_groups.assert_called_once() + mock_kc.get_group.assert_called_once_with('ns-group-id') + +def test_get_namespaces_with_perm_data_plane_no_ns_group(mock_keycloak_clients): + """Test when 'ns' group doesn't exist""" + mock_kc = mock_keycloak_clients['admin_client'] + + # Mock groups response without 'ns' group + mock_kc.get_groups.return_value = [ + {'id': 'group1', 'name': 'other-group'}, + {'id': 'group2', 'name': 'another-group'} + ] + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == [] + mock_kc.get_groups.assert_called_once() + mock_kc.get_group.assert_not_called() + +def test_get_namespaces_with_perm_data_plane_no_subgroups(mock_keycloak_clients): + """Test when 'ns' group has no subgroups""" + mock_kc = mock_keycloak_clients['admin_client'] + + mock_kc.get_groups.return_value = [ + {'id': 'ns-group-id', 'name': 'ns'} + ] + + # Mock empty subgroups + mock_kc.get_group.return_value = {'subGroups': []} + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == [] + mock_kc.get_group.assert_called_once_with('ns-group-id') + +def test_get_namespaces_with_perm_data_plane_no_matching_namespaces(mock_keycloak_clients): + """Test when no namespaces match the perm-data-plane value""" + mock_kc = mock_keycloak_clients['admin_client'] + + mock_kc.get_groups.return_value = [ + {'id': 'ns-group-id', 'name': 'ns'} + ] + + mock_kc.get_group.return_value = { + 'subGroups': [ + { + 'name': 'namespace1', + 'attributes': { + 'perm-data-plane': ['other-dp'] + } + }, + { + 'name': 'namespace2', + 'attributes': { + 'perm-data-plane': ['different-dp'] + } + } + ] + } + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == [] + +def test_get_namespaces_with_perm_data_plane_missing_attributes(mock_keycloak_clients): + """Test namespaces with missing or empty attributes""" + mock_kc = mock_keycloak_clients['admin_client'] + + mock_kc.get_groups.return_value = [ + {'id': 'ns-group-id', 'name': 'ns'} + ] + + mock_kc.get_group.return_value = { + 'subGroups': [ + { + 'name': 'namespace1', + 'attributes': { + 'perm-data-plane': ['test-dp'] + } + }, + { + 'name': 'namespace2', + # Missing attributes + }, + { + 'name': 'namespace3', + 'attributes': { + # Missing perm-data-plane + 'other-attr': ['value'] + } + }, + { + 'name': 'namespace4', + 'attributes': { + 'perm-data-plane': [] # Empty list + } + } + ] + } + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == ['namespace1'] + +def test_get_namespaces_with_perm_data_plane_missing_subgroups_key(mock_keycloak_clients): + """Test when get_group response doesn't have subGroups key""" + mock_kc = mock_keycloak_clients['admin_client'] + + mock_kc.get_groups.return_value = [ + {'id': 'ns-group-id', 'name': 'ns'} + ] + + # Mock response without subGroups key + mock_kc.get_group.return_value = {} + + result = get_namespaces_with_perm_data_plane('test-dp') + + assert result == []