Skip to content

Commit 38b1407

Browse files
committed
Move the byoidc secret to a different ns and address coderabbit comments
1 parent 8d9b3d7 commit 38b1407

5 files changed

Lines changed: 148 additions & 140 deletions

File tree

tests/model_registry/conftest.py

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@
3434
from pytest_testconfig import config as py_config
3535
from model_registry.types import RegisteredModel
3636

37-
from tests.model_registry.rbac.utils import wait_for_oauth_openshift_deployment, get_byoidc_user_credentials
38-
from tests.model_registry.utils import generate_namespace_name, get_rest_headers, wait_for_default_resource_cleanedup
37+
from tests.model_registry.rbac.utils import wait_for_oauth_openshift_deployment
38+
from tests.model_registry.utils import (
39+
generate_namespace_name,
40+
get_rest_headers,
41+
wait_for_default_resource_cleanedup,
42+
get_byoidc_user_credentials,
43+
)
3944
from utilities.general import generate_random_name, wait_for_pods_running
4045

4146
from tests.model_registry.constants import (
@@ -486,7 +491,7 @@ def test_idp_user(
486491
original_user: str,
487492
api_server_url: str,
488493
is_byoidc: bool,
489-
) -> Generator[UserTestSession, None, None]:
494+
) -> Generator[UserTestSession | None, None, None]:
490495
"""
491496
Session-scoped fixture that creates a test IDP user and cleans it up after all tests.
492497
Returns a UserTestSession object that contains all necessary credentials and contexts.
@@ -545,56 +550,62 @@ def original_user() -> str:
545550

546551
@pytest.fixture(scope="module")
547552
def created_htpasswd_secret(
548-
original_user: str, user_credentials_rbac: dict[str, str]
549-
) -> Generator[UserTestSession, None, None]:
553+
is_byoidc: bool, original_user: str, user_credentials_rbac: dict[str, str]
554+
) -> Generator[UserTestSession | None, None, None]:
550555
"""
551556
Session-scoped fixture that creates a test IDP user and cleans it up after all tests.
552557
Returns a UserTestSession object that contains all necessary credentials and contexts.
553558
"""
559+
if is_byoidc:
560+
yield
554561

555-
temp_path, htpasswd_b64 = create_htpasswd_file(
556-
username=user_credentials_rbac["username"], password=user_credentials_rbac["password"]
557-
)
558-
try:
559-
LOGGER.info(f"Creating secret {user_credentials_rbac['secret_name']} in openshift-config namespace")
560-
with Secret(
561-
name=user_credentials_rbac["secret_name"],
562-
namespace="openshift-config",
563-
htpasswd=htpasswd_b64,
564-
type="Opaque",
565-
wait_for_resource=True,
566-
) as secret:
567-
yield secret
568-
finally:
569-
# Clean up the temporary file
570-
temp_path.unlink(missing_ok=True)
562+
else:
563+
temp_path, htpasswd_b64 = create_htpasswd_file(
564+
username=user_credentials_rbac["username"], password=user_credentials_rbac["password"]
565+
)
566+
try:
567+
LOGGER.info(f"Creating secret {user_credentials_rbac['secret_name']} in openshift-config namespace")
568+
with Secret(
569+
name=user_credentials_rbac["secret_name"],
570+
namespace="openshift-config",
571+
htpasswd=htpasswd_b64,
572+
type="Opaque",
573+
wait_for_resource=True,
574+
) as secret:
575+
yield secret
576+
finally:
577+
# Clean up the temporary file
578+
temp_path.unlink(missing_ok=True)
571579

572580

573581
@pytest.fixture(scope="module")
574582
def updated_oauth_config(
575-
admin_client: DynamicClient, original_user: str, user_credentials_rbac: dict[str, str]
583+
is_byoidc: bool, admin_client: DynamicClient, original_user: str, user_credentials_rbac: dict[str, str]
576584
) -> Generator[Any, None, None]:
577-
# Get current providers and add the new one
578-
oauth = OAuth(name="cluster")
579-
identity_providers = oauth.instance.spec.identityProviders
580-
581-
new_idp = {
582-
"name": user_credentials_rbac["idp_name"],
583-
"mappingMethod": "claim",
584-
"type": "HTPasswd",
585-
"htpasswd": {"fileData": {"name": user_credentials_rbac["secret_name"]}},
586-
}
587-
updated_providers = identity_providers + [new_idp]
588-
589-
LOGGER.info("Updating OAuth")
590-
identity_providers_patch = ResourceEditor(patches={oauth: {"spec": {"identityProviders": updated_providers}}})
591-
identity_providers_patch.update(backup_resources=True)
592-
# Wait for OAuth server to be ready
593-
wait_for_oauth_openshift_deployment()
594-
LOGGER.info(f"Added IDP {user_credentials_rbac['idp_name']} to OAuth configuration")
595-
yield
596-
identity_providers_patch.restore()
597-
wait_for_oauth_openshift_deployment()
585+
if is_byoidc:
586+
yield
587+
else:
588+
# Get current providers and add the new one
589+
oauth = OAuth(name="cluster")
590+
identity_providers = oauth.instance.spec.identityProviders
591+
592+
new_idp = {
593+
"name": user_credentials_rbac["idp_name"],
594+
"mappingMethod": "claim",
595+
"type": "HTPasswd",
596+
"htpasswd": {"fileData": {"name": user_credentials_rbac["secret_name"]}},
597+
}
598+
updated_providers = identity_providers + [new_idp]
599+
600+
LOGGER.info("Updating OAuth")
601+
identity_providers_patch = ResourceEditor(patches={oauth: {"spec": {"identityProviders": updated_providers}}})
602+
identity_providers_patch.update(backup_resources=True)
603+
# Wait for OAuth server to be ready
604+
wait_for_oauth_openshift_deployment()
605+
LOGGER.info(f"Added IDP {user_credentials_rbac['idp_name']} to OAuth configuration")
606+
yield
607+
identity_providers_patch.restore()
608+
wait_for_oauth_openshift_deployment()
598609

599610

600611
@pytest.fixture(scope="module")

tests/model_registry/model_catalog/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
REDHAT_AI_CATALOG_ID,
1919
)
2020
from tests.model_registry.constants import CUSTOM_CATALOG_ID1
21-
from tests.model_registry.rbac.utils import get_mr_user_token
2221
from tests.model_registry.utils import (
2322
get_rest_headers,
2423
is_model_catalog_ready,
2524
get_model_catalog_pod,
2625
wait_for_model_catalog_api,
2726
execute_get_command,
2827
get_model_str,
28+
get_mr_user_token,
2929
)
3030
from utilities.infra import get_openshift_token, create_inference_token, login_with_user_password
3131

tests/model_registry/rbac/test_mr_rbac.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,14 @@
2727
build_mr_client_args,
2828
assert_positive_mr_registry,
2929
assert_forbidden_access,
30-
get_mr_user_token,
3130
)
3231
from tests.model_registry.constants import NUM_MR_INSTANCES
3332
from utilities.infra import get_openshift_token
3433
from mr_openapi.exceptions import ForbiddenException
3534
from utilities.user_utils import UserTestSession
3635
from kubernetes.dynamic import DynamicClient
3736
from ocp_resources.model_registry_modelregistry_opendatahub_io import ModelRegistry
38-
from tests.model_registry.utils import get_mr_service_by_label, get_endpoint_from_mr_service
37+
from tests.model_registry.utils import get_mr_service_by_label, get_endpoint_from_mr_service, get_mr_user_token
3938
from tests.model_registry.rbac.utils import grant_mr_access, revoke_mr_access
4039
from utilities.constants import Protocols
4140

tests/model_registry/rbac/utils.py

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import base64
2-
3-
import requests
41
from typing import Any, Dict, Generator, List
52

63
from kubernetes.dynamic import DynamicClient
@@ -9,11 +6,10 @@
96
from ocp_resources.deployment import Deployment
107
from ocp_resources.role import Role
118
from ocp_resources.role_binding import RoleBinding
12-
from ocp_resources.secret import Secret
139
from utilities.constants import Protocols
1410
import logging
1511
from model_registry import ModelRegistry as ModelRegistryClient
16-
from utilities.infra import get_openshift_token, get_cluster_authentication
12+
from utilities.infra import get_openshift_token
1713
from mr_openapi.exceptions import ForbiddenException
1814

1915
LOGGER = logging.getLogger(__name__)
@@ -182,91 +178,3 @@ def assert_forbidden_access(endpoint: str, token: str) -> None:
182178
except ForbiddenException:
183179
# This is what we want - access is properly forbidden
184180
pass
185-
186-
187-
def get_byoidc_issuer_url(admin_client: DynamicClient) -> str:
188-
authentication = get_cluster_authentication(admin_client=admin_client)
189-
assert authentication is not None
190-
url = authentication.instance.spec.oidcProviders[0].issuer.issuerURL
191-
assert url is not None
192-
return url
193-
194-
195-
def get_mr_user_token(admin_client: DynamicClient, user_credentials_rbac: dict[str, str]) -> str:
196-
url = f"{get_byoidc_issuer_url(admin_client=admin_client)}/protocol/openid-connect/token"
197-
headers = {"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "python-requests"}
198-
199-
data = {
200-
"username": user_credentials_rbac["username"],
201-
"password": user_credentials_rbac["password"],
202-
"grant_type": "password",
203-
"client_id": "oc-cli",
204-
"scope": "openid",
205-
}
206-
207-
try:
208-
LOGGER.info(f"Requesting token for user {user_credentials_rbac['username']} in byoidc environment")
209-
response = requests.post(
210-
url=url,
211-
headers=headers,
212-
data=data,
213-
allow_redirects=True,
214-
timeout=30,
215-
verify=True, # Set to False if you need to skip SSL verification
216-
)
217-
response.raise_for_status()
218-
json_response = response.json()
219-
220-
# Validate that we got an access token
221-
if "id_token" not in json_response:
222-
LOGGER.error("Warning: No access_token in response")
223-
raise AssertionError(f"No access_token in response: {json_response}")
224-
return json_response["id_token"]
225-
except Exception as e:
226-
raise e
227-
228-
229-
def get_byoidc_user_credentials(username: str = None) -> Dict[str, str]:
230-
"""
231-
Get user credentials from byoidc-credentials secret.
232-
233-
Args:
234-
username: Specific username to look up. If None, returns first user.
235-
236-
Returns:
237-
Dictionary with username and password for the specified user.
238-
239-
Raises:
240-
ValueError: If username not found or no users/passwords in secret.
241-
AssertionError: If users or passwords lists are empty.
242-
"""
243-
credentials_secret = Secret(name="byoidc-credentials", namespace="default", ensure_exists=True)
244-
credential_data = credentials_secret.instance.data
245-
user_names = base64.b64decode(credential_data.users).decode().split(",")
246-
passwords = base64.b64decode(credential_data.passwords).decode().split(",")
247-
248-
# Assert that both lists are not empty
249-
assert user_names and user_names != [""], "No usernames found in byoidc-credentials secret"
250-
assert passwords and passwords != [""], "No passwords found in byoidc-credentials secret"
251-
252-
# Use specified username or default to first user
253-
requested_username = username if username else user_names[0]
254-
255-
if requested_username and requested_username in user_names:
256-
# Find the index of the requested username
257-
user_index = user_names.index(requested_username)
258-
if user_index < len(passwords):
259-
selected_username = user_names[user_index]
260-
selected_password = passwords[user_index]
261-
else:
262-
raise ValueError(f"Password not found for user '{requested_username}' at index {user_index}")
263-
elif requested_username:
264-
raise ValueError(f"Username '{requested_username}' not found in byoidc credentials")
265-
else:
266-
raise ValueError("No users found in byoidc credentials")
267-
268-
LOGGER.info(f"Using byoidc-credentials username='{selected_username}'")
269-
return {
270-
"username": selected_username,
271-
"password": selected_password,
272-
}

tests/model_registry/utils.py

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import base64
12
import json
23
import time
3-
from typing import Any, List
4+
from typing import Any, List, Dict
45

56
import requests
67
from kubernetes.dynamic import DynamicClient
@@ -33,6 +34,7 @@
3334
from model_registry.types import RegisteredModel
3435

3536
from utilities.general import wait_for_pods_running
37+
from utilities.infra import get_cluster_authentication
3638

3739
ADDRESS_ANNOTATION_PREFIX: str = "routing.opendatahub.io/external-address-"
3840
MARIA_DB_IMAGE = (
@@ -797,3 +799,91 @@ def wait_for_default_resource_cleanedup(admin_client: DynamicClient, namespace_n
797799
if not objects_not_deleted:
798800
return True
799801
raise ResourceNotDeleted(f"Following objects are not deleted: {objects_not_deleted}")
802+
803+
804+
def get_mr_user_token(admin_client: DynamicClient, user_credentials_rbac: dict[str, str]) -> str:
805+
url = f"{get_byoidc_issuer_url(admin_client=admin_client)}/protocol/openid-connect/token"
806+
headers = {"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "python-requests"}
807+
808+
data = {
809+
"username": user_credentials_rbac["username"],
810+
"password": user_credentials_rbac["password"],
811+
"grant_type": "password",
812+
"client_id": "oc-cli",
813+
"scope": "openid",
814+
}
815+
816+
try:
817+
LOGGER.info(f"Requesting token for user {user_credentials_rbac['username']} in byoidc environment")
818+
response = requests.post(
819+
url=url,
820+
headers=headers,
821+
data=data,
822+
allow_redirects=True,
823+
timeout=30,
824+
verify=True, # Set to False if you need to skip SSL verification
825+
)
826+
response.raise_for_status()
827+
json_response = response.json()
828+
829+
# Validate that we got an access token
830+
if "id_token" not in json_response:
831+
LOGGER.error("Warning: No access_token in response")
832+
raise AssertionError(f"No access_token in response: {json_response}")
833+
return json_response["id_token"]
834+
except Exception as e:
835+
raise e
836+
837+
838+
def get_byoidc_issuer_url(admin_client: DynamicClient) -> str:
839+
authentication = get_cluster_authentication(admin_client=admin_client)
840+
assert authentication is not None
841+
url = authentication.instance.spec.oidcProviders[0].issuer.issuerURL
842+
assert url is not None
843+
return url
844+
845+
846+
def get_byoidc_user_credentials(username: str = None) -> Dict[str, str]:
847+
"""
848+
Get user credentials from byoidc-credentials secret.
849+
850+
Args:
851+
username: Specific username to look up. If None, returns first user.
852+
853+
Returns:
854+
Dictionary with username and password for the specified user.
855+
856+
Raises:
857+
ValueError: If username not found or no users/passwords in secret.
858+
AssertionError: If users or passwords lists are empty.
859+
"""
860+
credentials_secret = Secret(name="byoidc-credentials", namespace="oidc", ensure_exists=True)
861+
credential_data = credentials_secret.instance.data
862+
user_names = base64.b64decode(credential_data.users).decode().split(",")
863+
passwords = base64.b64decode(credential_data.passwords).decode().split(",")
864+
865+
# Assert that both lists are not empty
866+
assert user_names and user_names != [""], "No usernames found in byoidc-credentials secret"
867+
assert passwords and passwords != [""], "No passwords found in byoidc-credentials secret"
868+
869+
# Use specified username or default to first user
870+
requested_username = username if username else user_names[0]
871+
872+
if requested_username and requested_username in user_names:
873+
# Find the index of the requested username
874+
user_index = user_names.index(requested_username)
875+
if user_index < len(passwords):
876+
selected_username = user_names[user_index]
877+
selected_password = passwords[user_index]
878+
else:
879+
raise ValueError(f"Password not found for user '{requested_username}' at index {user_index}")
880+
elif requested_username:
881+
raise ValueError(f"Username '{requested_username}' not found in byoidc credentials")
882+
else:
883+
raise ValueError("No users found in byoidc credentials")
884+
885+
LOGGER.info(f"Using byoidc-credentials username='{selected_username}'")
886+
return {
887+
"username": selected_username,
888+
"password": selected_password,
889+
}

0 commit comments

Comments
 (0)