1414from charmed_kubeflow_chisme .testing import (
1515 assert_alert_rules ,
1616 assert_metrics_endpoint ,
17+ assert_security_context ,
1718 deploy_and_assert_grafana_agent ,
19+ generate_container_securitycontext_map ,
1820 get_alert_rules ,
21+ get_pod_names ,
1922)
23+ from jinja2 import Template
24+ from lightkube import Client
2025from lightkube .resources .apiextensions_v1 import CustomResourceDefinition
2126from lightkube .resources .rbac_authorization_v1 import ClusterRole
2227from pytest_operator .plugin import OpsTest
2328
2429logger = logging .getLogger (__name__ )
2530
26- APP_NAME = "training-operator"
31+ METADATA = yaml .safe_load (Path ("./metadata.yaml" ).read_text ())
32+ DEPLOYMENT_FILE = Path ("./src/templates/deployment.yaml.j2" ).read_text ()
33+ APP_NAME = METADATA ["name" ]
2734CHARM_LOCATION = None
2835APP_PREVIOUS_CHANNEL = "1.8/stable"
2936METRICS_PATH = "/metrics"
3037METRICS_PORT = 8080
38+ WEBHOOK_TARGET_PORT = "9443"
39+ DEPLOYMENT_YAML = yaml .safe_load (
40+ Template (DEPLOYMENT_FILE ).render (
41+ ** {
42+ "app_name" : APP_NAME ,
43+ "metrics_port" : METRICS_PORT ,
44+ "webhook_target_port" : WEBHOOK_TARGET_PORT ,
45+ }
46+ )
47+ )
48+
49+
50+ @pytest .fixture (scope = "session" )
51+ def lightkube_client () -> Client :
52+ """Returns lightkube Kubernetes client"""
53+ client = Client (field_manager = f"{ APP_NAME } " )
54+ return client
3155
3256
3357@pytest .mark .abort_on_fail
@@ -190,7 +214,7 @@ async def test_alert_rules(ops_test: OpsTest):
190214 await assert_alert_rules (app , alert_rules )
191215
192216
193- async def test_metrics_enpoint (ops_test : OpsTest ):
217+ async def test_metrics_endpoint (ops_test : OpsTest ):
194218 """Test metrics_endpoints are defined in relation data bag and their accessibility.
195219
196220 This function gets all the metrics_endpoints from the relation data bag, checks if
@@ -204,6 +228,57 @@ async def test_metrics_enpoint(ops_test: OpsTest):
204228 await assert_metrics_endpoint (app , metrics_port = METRICS_PORT , metrics_path = METRICS_PATH )
205229
206230
231+ def build_pod_container_map (model_name : str , deployment_template : dict ) -> dict [str , dict ]:
232+ """Build full map of pods:containers belonging to this charm.
233+
234+ This function builds a custom mapping of security context for pods and containers,
235+ necessary because some pods are not directly spawned by juju but are defined in
236+ `src/templates/deployment.yaml.j2`.
237+ """
238+ charm_pods : list = get_pod_names (model_name , APP_NAME )
239+ deployment_pods : list = get_pod_names (model_name , f"{ APP_NAME } -manager" )
240+ deployment_container_name = deployment_template ["spec" ]["template" ]["spec" ]["containers" ][0 ][
241+ "name"
242+ ]
243+ deployment_container_security_context = deployment_template ["spec" ]["template" ]["spec" ][
244+ "containers"
245+ ][0 ]["securityContext" ]
246+ pod_container_map = {}
247+
248+ for charm_pod in charm_pods :
249+ pod_container_map [charm_pod ] = generate_container_securitycontext_map (METADATA )
250+ for pod in deployment_pods :
251+ pod_container_map [pod ] = {deployment_container_name : deployment_container_security_context }
252+ return pod_container_map
253+
254+
255+ async def test_container_security_context (
256+ ops_test : OpsTest ,
257+ lightkube_client : Client ,
258+ ):
259+ """Test container security context is correctly set.
260+
261+ Verify that container spec defines the security context with correct
262+ user ID and group ID.
263+ """
264+ failed_checks = []
265+ pod_container_map = build_pod_container_map (ops_test .model_name , DEPLOYMENT_YAML )
266+ for pod , pod_containers in pod_container_map .items ():
267+ for container in pod_containers .keys ():
268+ try :
269+ logger .info ("Checking security context for container %s (pod: %s)" , container , pod )
270+ assert_security_context (
271+ lightkube_client ,
272+ pod ,
273+ container ,
274+ pod_containers ,
275+ ops_test .model_name ,
276+ )
277+ except AssertionError as err :
278+ failed_checks .append (f"{ pod } /{ container } : { err } " )
279+ assert failed_checks == []
280+
281+
207282@pytest .mark .abort_on_fail
208283async def test_remove_with_resources_present (ops_test : OpsTest ):
209284 """Test remove with all resources deployed.
0 commit comments