11"""Fixtures for Model Registry Python Client Signing Tests."""
22
33import json
4+ import logging
5+ import os
46from collections .abc import Generator
7+ from pathlib import Path
58from typing import Any
69
710import pytest
811import requests
12+ from huggingface_hub import snapshot_download
913from kubernetes .dynamic import DynamicClient
14+ from model_registry .signing import Signer
1015from ocp_resources .config_map import ConfigMap
1116from ocp_resources .deployment import Deployment
1217from ocp_resources .namespace import Namespace
2328)
2429from tests .model_registry .model_registry .python_client .signing .utils import (
2530 create_connection_type_field ,
31+ generate_token ,
2632 get_organization_config ,
33+ get_root_checksum ,
2734 get_tas_service_urls ,
2835)
2936from utilities .constants import OPENSHIFT_OPERATORS , Timeout
30- from utilities .infra import get_openshift_token
37+ from utilities .infra import get_openshift_token , is_managed_cluster
3138from utilities .resources .securesign import Securesign
3239
3340LOGGER = get_logger (name = __name__ )
3441
3542
36- @pytest .fixture (scope = "class" )
43+ @pytest .fixture (scope = "package" )
44+ def skip_if_not_managed_cluster (admin_client : DynamicClient ) -> None :
45+ """
46+ Skip tests if the cluster is not managed.
47+ """
48+ if not is_managed_cluster (admin_client ):
49+ pytest .skip ("Skipping tests - cluster is not managed" )
50+
51+ LOGGER .info ("Cluster is managed - proceeding with tests" )
52+
53+
54+ @pytest .fixture (scope = "package" )
3755def oidc_issuer_url (admin_client : DynamicClient , api_server_url : str ) -> str :
3856 """Get the OIDC issuer URL from cluster's .well-known/openid-configuration endpoint.
3957
@@ -61,7 +79,7 @@ def oidc_issuer_url(admin_client: DynamicClient, api_server_url: str) -> str:
6179 return issuer
6280
6381
64- @pytest .fixture (scope = "class " )
82+ @pytest .fixture (scope = "package " )
6583def installed_tas_operator (admin_client : DynamicClient ) -> Generator [None , Any ]:
6684 """Install Red Hat Trusted Artifact Signer (RHTAS/TAS) operator if not already installed.
6785
@@ -126,7 +144,7 @@ def installed_tas_operator(admin_client: DynamicClient) -> Generator[None, Any]:
126144 yield
127145
128146
129- @pytest .fixture (scope = "class " )
147+ @pytest .fixture (scope = "package " )
130148def securesign_instance (
131149 admin_client : DynamicClient , installed_tas_operator : None , oidc_issuer_url : str
132150) -> Generator [Securesign , Any ]:
@@ -165,6 +183,7 @@ def securesign_instance(
165183 },
166184 "spec" : {
167185 "fulcio" : {
186+ "enabled" : True ,
168187 "externalAccess" : {"enabled" : True },
169188 "certificate" : org_config ,
170189 "config" : {
@@ -178,13 +197,18 @@ def securesign_instance(
178197 },
179198 },
180199 "rekor" : {
200+ "enabled" : True ,
181201 "externalAccess" : {"enabled" : True },
182202 },
183- "ctlog" : {},
203+ "ctlog" : {
204+ "enabled" : True ,
205+ },
184206 "tuf" : {
207+ "enabled" : True ,
185208 "externalAccess" : {"enabled" : True },
186209 },
187210 "tsa" : {
211+ "enabled" : True ,
188212 "externalAccess" : {"enabled" : True },
189213 "signer" : {
190214 "certificateChain" : {
@@ -207,7 +231,7 @@ def securesign_instance(
207231 LOGGER .info (f"Securesign instance '{ SECURESIGN_NAME } ' cleanup completed" )
208232
209233
210- @pytest .fixture (scope = "class " )
234+ @pytest .fixture (scope = "package " )
211235def tas_connection_type (admin_client : DynamicClient , securesign_instance : Securesign ) -> Generator [ConfigMap , Any ]:
212236 """Create ODH Connection Type ConfigMap for TAS (Trusted Artifact Signer).
213237
@@ -288,3 +312,114 @@ def tas_connection_type(admin_client: DynamicClient, securesign_instance: Secure
288312 yield connection_type
289313
290314 LOGGER .info (f"TAS Connection Type '{ TAS_CONNECTION_TYPE_NAME } ' deleted from namespace '{ app_namespace } '" )
315+
316+
317+ @pytest .fixture (scope = "package" )
318+ def downloaded_model_dir () -> Path :
319+ """Download a test model from Hugging Face to a temporary directory.
320+
321+ Downloads the jonburdo/public-test-model-1 model to a temporary directory
322+ and yields the path to the downloaded model directory.
323+
324+ Yields:
325+ Path: Path to the temporary directory containing the downloaded model
326+ """
327+ model_dir = Path (py_config ["tmp_base_dir" ]) / "model"
328+ model_dir .mkdir (exist_ok = True )
329+
330+ LOGGER .info (f"Downloading model to temporary directory: { model_dir } " )
331+ snapshot_download (repo_id = "jonburdo/public-test-model-1" , local_dir = str (model_dir ))
332+ LOGGER .info (f"Model downloaded successfully to: { model_dir } " )
333+
334+ return model_dir
335+
336+
337+ @pytest .fixture (scope = "package" )
338+ def set_environment_variables (securesign_instance : Securesign ) -> Generator [None , Any ]:
339+ """
340+ Create a service account token and save it to a temporary directory.
341+ Automatically cleans up environment variables when fixture scope ends.
342+ """
343+ # Set up environment variables
344+ securesign_data = securesign_instance .instance .to_dict ()
345+ service_urls = get_tas_service_urls (securesign_instance = securesign_data )
346+ os .environ ["IDENTITY_TOKEN_PATH" ] = generate_token (temp_base_folder = py_config ["tmp_base_dir" ])
347+ os .environ ["SIGSTORE_TUF_URL" ] = service_urls ["tuf" ]
348+ os .environ ["SIGSTORE_FULCIO_URL" ] = service_urls ["fulcio" ]
349+ os .environ ["SIGSTORE_REKOR_URL" ] = service_urls ["rekor" ]
350+ os .environ ["SIGSTORE_TSA_URL" ] = service_urls ["tsa" ]
351+ os .environ ["ROOT_CHECKSUM" ] = get_root_checksum (sigstore_tuf_url = service_urls ["tuf" ])
352+ os .environ ["ROOT_URL" ] = os .environ ["SIGSTORE_TUF_URL" ] + "/root.json"
353+
354+ LOGGER .info ("Environment variables set for signing tests" )
355+ yield
356+
357+ # Clean up environment variables
358+ for var_name in [
359+ "IDENTITY_TOKEN_PATH" ,
360+ "SIGSTORE_TUF_URL" ,
361+ "SIGSTORE_FULCIO_URL" ,
362+ "SIGSTORE_REKOR_URL" ,
363+ "SIGSTORE_TSA_URL" ,
364+ "ROOT_CHECKSUM" ,
365+ "ROOT_URL" ,
366+ ]:
367+ os .environ .pop (var_name , None )
368+
369+ LOGGER .info ("Environment variables cleaned up" )
370+
371+
372+ @pytest .fixture (scope = "function" )
373+ def signer (set_environment_variables ) -> Signer :
374+ """Create and initialize a Signer instance for model signing.
375+
376+ Creates a Signer with identity token, root URL, and root checksum from environment
377+ variables set by the set_environment_variables fixture. Initializes the signer
378+ with force=True and debug logging.
379+
380+ Args:
381+ set_environment_variables: Fixture that sets up required environment variables
382+
383+ Returns:
384+ Signer: Initialized signer instance ready for model signing
385+
386+ Raises:
387+ Exception: If signer initialization fails
388+ """
389+ LOGGER .info (f"Creating Signer with token path: { os .environ ['IDENTITY_TOKEN_PATH' ]} " )
390+ LOGGER .info (f"Root URL: { os .environ ['ROOT_URL' ]} " )
391+
392+ signer = Signer (
393+ identity_token_path = os .environ ["IDENTITY_TOKEN_PATH" ],
394+ root_url = os .environ ["ROOT_URL" ],
395+ root_checksum = os .environ ["ROOT_CHECKSUM" ],
396+ log_level = logging .DEBUG ,
397+ )
398+
399+ LOGGER .info ("Initializing signer..." )
400+ signer .initialize (force = True )
401+ LOGGER .info ("Signer initialized successfully" )
402+
403+ return signer
404+
405+
406+ @pytest .fixture (scope = "function" )
407+ def signed_model (signer , downloaded_model_dir ) -> Path :
408+ """
409+ Use an initialized signer to sign the downloaded model.
410+ """
411+ LOGGER .info (f"Signing model in directory: { downloaded_model_dir } " )
412+ signer .sign_model (model_path = str (downloaded_model_dir ))
413+ LOGGER .info ("Model signed successfully" )
414+
415+ return downloaded_model_dir
416+
417+
418+ @pytest .fixture (scope = "function" )
419+ def verified_model (signer , downloaded_model_dir ) -> None :
420+ """
421+ Verify a signed model.
422+ """
423+ LOGGER .info (f"Verifying signed model in directory: { downloaded_model_dir } " )
424+ signer .verify_model (model_path = str (downloaded_model_dir ))
425+ LOGGER .info ("Model verified successfully" )
0 commit comments