@@ -337,6 +337,7 @@ impl v1alpha1::SparkApplication {
337337 logdir : & Option < ResolvedLogDir > ,
338338 log_config_map : Option < & str > ,
339339 requested_secret_lifetime : & Duration ,
340+ open_lineage_conn : Option < & openlineage:: ResolvedOpenLineageConnection > ,
340341 ) -> Result < Vec < Volume > , Error > {
341342 // Collect the volumes in a map keyed by name to avoid duplicates.
342343 // Duplicates can happen when the the S3 credentials volume and the history server log directory use the same secret class.
@@ -417,7 +418,7 @@ impl v1alpha1::SparkApplication {
417418 . build ( ) ,
418419 ) ;
419420 }
420- if let Some ( cert_secrets) = tlscerts:: tls_secret_names ( s3conn, logdir) {
421+ if let Some ( cert_secrets) = tlscerts:: tls_secret_names ( s3conn, logdir, open_lineage_conn ) {
421422 result. insert (
422423 STACKABLE_TRUST_STORE_NAME . to_string ( ) ,
423424 VolumeBuilder :: new ( STACKABLE_TRUST_STORE_NAME )
@@ -470,6 +471,7 @@ impl v1alpha1::SparkApplication {
470471 & self ,
471472 s3conn : & Option < s3:: v1alpha1:: ConnectionSpec > ,
472473 logdir : & Option < ResolvedLogDir > ,
474+ open_lineage_conn : Option < & openlineage:: ResolvedOpenLineageConnection > ,
473475 ) -> Vec < VolumeMount > {
474476 let mut tmpl_mounts = vec ! [
475477 VolumeMount {
@@ -484,7 +486,8 @@ impl v1alpha1::SparkApplication {
484486 } ,
485487 ] ;
486488
487- tmpl_mounts = self . add_common_volume_mounts ( tmpl_mounts, s3conn, logdir, false ) ;
489+ tmpl_mounts =
490+ self . add_common_volume_mounts ( tmpl_mounts, s3conn, logdir, false , open_lineage_conn) ;
488491
489492 if let Some ( CommonConfiguration {
490493 config :
@@ -510,6 +513,7 @@ impl v1alpha1::SparkApplication {
510513 s3conn : & Option < s3:: v1alpha1:: ConnectionSpec > ,
511514 logdir : & Option < ResolvedLogDir > ,
512515 logging_enabled : bool ,
516+ open_lineage_conn : Option < & openlineage:: ResolvedOpenLineageConnection > ,
513517 ) -> Vec < VolumeMount > {
514518 if self . spec . image . is_some ( ) {
515519 mounts. push ( VolumeMount {
@@ -568,7 +572,7 @@ impl v1alpha1::SparkApplication {
568572 ..VolumeMount :: default ( )
569573 } ) ;
570574 }
571- if let Some ( cert_secrets) = tlscerts:: tls_secret_names ( s3conn, logdir) {
575+ if let Some ( cert_secrets) = tlscerts:: tls_secret_names ( s3conn, logdir, open_lineage_conn ) {
572576 mounts. push ( VolumeMount {
573577 name : STACKABLE_TRUST_STORE_NAME . into ( ) ,
574578 mount_path : STACKABLE_TRUST_STORE . into ( ) ,
@@ -606,17 +610,18 @@ impl v1alpha1::SparkApplication {
606610 let name = self . metadata . name . clone ( ) . context ( ObjectHasNoNameSnafu ) ?;
607611
608612 // Commands needed to build the p12 trust store from the secret class certs configured for
609- // S3 connections.
610- let build_truststore_commands = match tlscerts:: tls_secret_names ( s3conn, log_dir) {
611- Some ( cert_secrets) => {
612- let mut build_truststore_str =
613- vec ! [ tlscerts:: convert_system_trust_store_to_pkcs12( ) ] ;
614- build_truststore_str
615- . extend ( cert_secrets. into_iter ( ) . map ( tlscerts:: import_truststore) ) ;
616- format ! ( "{};" , build_truststore_str. join( " && " ) )
617- }
618- None => "" . to_string ( ) ,
619- } ;
613+ // S3 connections, the log directory, and the OpenLineage backend connection.
614+ let build_truststore_commands =
615+ match tlscerts:: tls_secret_names ( s3conn, log_dir, open_lineage_conn) {
616+ Some ( cert_secrets) => {
617+ let mut build_truststore_str =
618+ vec ! [ tlscerts:: convert_system_trust_store_to_pkcs12( ) ] ;
619+ build_truststore_str
620+ . extend ( cert_secrets. into_iter ( ) . map ( tlscerts:: import_truststore) ) ;
621+ format ! ( "{};" , build_truststore_str. join( " && " ) )
622+ }
623+ None => "" . to_string ( ) ,
624+ } ;
620625
621626 let mut submit_cmd = vec ! [
622627 format!(
@@ -698,7 +703,13 @@ impl v1alpha1::SparkApplication {
698703 }
699704
700705 let ( driver_extra_java_options, executor_extra_java_options) =
701- construct_extra_java_options ( self , s3conn, log_dir, product_version) ?;
706+ construct_extra_java_options (
707+ self ,
708+ s3conn,
709+ log_dir,
710+ product_version,
711+ open_lineage_conn,
712+ ) ?;
702713 submit_cmd. extend ( vec ! [
703714 format!( "--conf spark.driver.extraJavaOptions=\" {driver_extra_java_options}\" " ) ,
704715 format!( "--conf spark.executor.extraJavaOptions=\" {executor_extra_java_options}\" " ) ,
@@ -845,6 +856,7 @@ impl v1alpha1::SparkApplication {
845856 & self ,
846857 s3conn : & Option < s3:: v1alpha1:: ConnectionSpec > ,
847858 logdir : & Option < ResolvedLogDir > ,
859+ open_lineage_conn : Option < & openlineage:: ResolvedOpenLineageConnection > ,
848860 ) -> Vec < EnvVar > {
849861 let mut e: Vec < EnvVar > = self . spec . env . clone ( ) ;
850862
@@ -877,7 +889,7 @@ impl v1alpha1::SparkApplication {
877889 value_from : None ,
878890 } ) ;
879891 }
880- if tlscerts:: tls_secret_names ( s3conn, logdir) . is_some ( ) {
892+ if tlscerts:: tls_secret_names ( s3conn, logdir, open_lineage_conn ) . is_some ( ) {
881893 e. push ( EnvVar {
882894 name : "STACKABLE_TLS_STORE_PASSWORD" . to_string ( ) ,
883895 value : Some ( STACKABLE_TLS_STORE_PASSWORD . to_string ( ) ) ,
@@ -1627,7 +1639,7 @@ spec:
16271639 "# } )
16281640 . unwrap ( ) ;
16291641
1630- let got = spark_application. spark_job_volume_mounts ( & None , & None ) ;
1642+ let got = spark_application. spark_job_volume_mounts ( & None , & None , None ) ;
16311643
16321644 let expected = vec ! [
16331645 VolumeMount {
@@ -1875,6 +1887,63 @@ spec:
18751887 ) ;
18761888 }
18771889
1890+ /// The OpenLineage connection's `caCert.secretClass` must be wired into the driver truststore,
1891+ /// so the OpenLineage listener's HTTPS POST to the backend trusts the secret-operator cert.
1892+ /// Without this the `https` scheme above is unusable (handshake fails against the default JVM
1893+ /// truststore).
1894+ #[ test]
1895+ fn test_openlineage_tls_secret_class_wired_into_truststore ( ) {
1896+ let yaml = indoc ! { r#"
1897+ ---
1898+ apiVersion: spark.stackable.tech/v1alpha1
1899+ kind: SparkApplication
1900+ metadata:
1901+ name: spark-examples
1902+ namespace: default
1903+ spec:
1904+ mode: cluster
1905+ mainApplicationFile: test.py
1906+ sparkImage:
1907+ productVersion: 1.2.3
1908+ openLineage:
1909+ connection:
1910+ inline:
1911+ host: marquez
1912+ port: 5000
1913+ tls:
1914+ verification:
1915+ server:
1916+ caCert:
1917+ secretClass: marquez-ca
1918+ "# } ;
1919+ let command = build_command_with_openlineage ( yaml, "4.1.2" ) ;
1920+
1921+ // The mounted secret-operator truststore for `marquez-ca` is imported into the shared
1922+ // PKCS12 truststore.
1923+ assert ! ( command. contains( & format!(
1924+ "{STACKABLE_MOUNT_PATH_TLS}/marquez-ca/truststore.p12"
1925+ ) ) ) ;
1926+ // The driver + executor JVMs are pointed at that truststore.
1927+ assert_eq ! (
1928+ command
1929+ . matches( & format!(
1930+ "-Djavax.net.ssl.trustStore={STACKABLE_TRUST_STORE}/truststore.p12"
1931+ ) )
1932+ . count( ) ,
1933+ 2 ,
1934+ ) ;
1935+ }
1936+
1937+ /// Without an OpenLineage TLS `secretClass` (and no S3/logdir), no truststore is built and the
1938+ /// driver JVM gets no truststore options.
1939+ #[ test]
1940+ fn test_openlineage_without_tls_has_no_truststore ( ) {
1941+ let command = build_command_with_openlineage ( OPENLINEAGE_ENABLED_APP , "4.1.2" ) ;
1942+
1943+ assert ! ( !command. contains( "-Djavax.net.ssl.trustStore=" ) ) ;
1944+ assert ! ( !command. contains( STACKABLE_MOUNT_PATH_TLS ) ) ;
1945+ }
1946+
18781947 #[ rstest]
18791948 #[ case:: explicit_app_name(
18801949 indoc! { r#"
0 commit comments