Skip to content

Commit df6084b

Browse files
committed
WIP
1 parent eace189 commit df6084b

17 files changed

Lines changed: 759 additions & 5 deletions

File tree

crates/stackable-operator/src/builder/pod/container.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt;
1+
use std::{borrow::Borrow, fmt};
22

33
use indexmap::IndexMap;
44
use k8s_openapi::api::core::v1::{
@@ -175,8 +175,14 @@ impl ContainerBuilder {
175175
self
176176
}
177177

178-
pub fn add_env_vars(&mut self, env_vars: Vec<EnvVar>) -> &mut Self {
179-
self.env.get_or_insert_with(Vec::new).extend(env_vars);
178+
pub fn add_env_vars<I>(&mut self, env_vars: I) -> &mut Self
179+
where
180+
I: IntoIterator,
181+
I::Item: std::borrow::Borrow<EnvVar>,
182+
{
183+
self.env
184+
.get_or_insert_with(Vec::new)
185+
.extend(env_vars.into_iter().map(|e| e.borrow().clone()));
180186
self
181187
}
182188

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use k8s_openapi::api::core::v1::{EnvVar, EnvVarSource, SecretKeySelector};
2+
3+
pub fn env_var_from_secret(
4+
env_var_name: impl Into<String>,
5+
secret_name: impl Into<String>,
6+
secret_key: impl Into<String>,
7+
) -> EnvVar {
8+
EnvVar {
9+
name: env_var_name.into(),
10+
value_from: Some(EnvVarSource {
11+
secret_key_ref: Some(SecretKeySelector {
12+
name: secret_name.into(),
13+
key: secret_key.into(),
14+
..Default::default()
15+
}),
16+
..Default::default()
17+
}),
18+
..Default::default()
19+
}
20+
}

crates/stackable-operator/src/builder/pod/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::{
2929
};
3030

3131
pub mod container;
32+
pub mod env;
3233
pub mod probe;
3334
pub mod resources;
3435
pub mod security;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use k8s_openapi::api::core::v1::EnvVar;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::builder::pod::{container::ContainerBuilder, env::env_var_from_secret};
6+
7+
/// TODO docs
8+
pub trait CeleryDatabaseConnection {
9+
/// TODO docs, e.g. on what are valid characters for unique_database_name
10+
fn celery_connection_details(
11+
&self,
12+
unique_database_name: &str,
13+
) -> CeleryDatabaseConnectionDetails;
14+
}
15+
16+
pub struct CeleryDatabaseConnectionDetails {
17+
/// The connection URI, which can contain env variable templates, e.g.
18+
/// `redis://:${METADATA_DATABASE_PASSWORD}@airflow-redis-master:6379/0`
19+
/// or
20+
/// `<generic URI from the user>`.
21+
pub uri_template: String,
22+
23+
/// The [`EnvVar`]s the operator needs to mount into the created Pods.
24+
pub env_vars: Vec<EnvVar>,
25+
}
26+
27+
impl CeleryDatabaseConnectionDetails {
28+
pub fn add_to_container(&self, cb: &mut ContainerBuilder) {
29+
cb.add_env_vars(self.env_vars.iter());
30+
}
31+
}
32+
33+
/// TODO docs
34+
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
35+
#[serde(rename_all = "camelCase")]
36+
pub struct GenericCeleryDatabaseConnection {
37+
/// The name of the Secret that contains an `uri` key with the complete SQLAlchemy URI.
38+
pub uri_secret: String,
39+
}
40+
41+
impl CeleryDatabaseConnection for GenericCeleryDatabaseConnection {
42+
fn celery_connection_details(
43+
&self,
44+
unique_database_name: &str,
45+
) -> CeleryDatabaseConnectionDetails {
46+
let uri_env_name = format!(
47+
"{upper}_DATABASE_URI",
48+
upper = unique_database_name.to_uppercase()
49+
);
50+
let uri_env_var = env_var_from_secret(&uri_env_name, &self.uri_secret, "uri");
51+
52+
CeleryDatabaseConnectionDetails {
53+
uri_template: format!("${{{uri_env_name}}}"),
54+
env_vars: vec![uri_env_var],
55+
}
56+
}
57+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use k8s_openapi::api::core::v1::EnvVar;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
use url::Url;
5+
6+
use crate::{
7+
builder::pod::container::ContainerBuilder, crd::database::helpers::username_and_password_envs,
8+
};
9+
10+
/// TODO docs
11+
pub trait JDBCDatabaseConnection {
12+
/// TODO docs
13+
fn jdbc_connection_details(
14+
&self,
15+
unique_database_name: &str,
16+
) -> Result<JDBCDatabaseConnectionDetails, crate::crd::database::Error>;
17+
}
18+
19+
pub struct JDBCDatabaseConnectionDetails {
20+
/// The Java class name of the driver, e.g. `org.postgresql.Driver`
21+
pub driver: String,
22+
23+
/// The connection URI (without user and password), e.g.
24+
/// `jdbc:postgresql://airflow-postgresql:5432/airflow`
25+
pub connection_uri: Url,
26+
27+
/// The [`EnvVar`] that mounts the credentials Secret and provides the username.
28+
pub username_env: Option<EnvVar>,
29+
30+
/// The [`EnvVar`] that mounts the credentials Secret and provides the password.
31+
pub password_env: Option<EnvVar>,
32+
}
33+
34+
impl JDBCDatabaseConnectionDetails {
35+
pub fn add_to_container(&self, cb: &mut ContainerBuilder) {
36+
let env_vars = self.username_env.iter().chain(self.password_env.iter());
37+
cb.add_env_vars(env_vars);
38+
}
39+
}
40+
41+
/// TODO docs
42+
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
43+
#[serde(rename_all = "camelCase")]
44+
pub struct GenericJDBCDatabaseConnection {
45+
/// TODO docs
46+
pub driver: String,
47+
48+
/// TODO docs
49+
pub uri: Url,
50+
51+
/// TODO docs
52+
pub credentials_secret: String,
53+
}
54+
55+
impl JDBCDatabaseConnection for GenericJDBCDatabaseConnection {
56+
fn jdbc_connection_details(
57+
&self,
58+
unique_database_name: &str,
59+
) -> Result<JDBCDatabaseConnectionDetails, crate::crd::database::Error> {
60+
let (username_env, password_env) =
61+
username_and_password_envs(unique_database_name, &self.credentials_secret);
62+
63+
Ok(JDBCDatabaseConnectionDetails {
64+
driver: self.driver.clone(),
65+
connection_uri: self.uri.clone(),
66+
username_env: Some(username_env),
67+
password_env: Some(password_env),
68+
})
69+
}
70+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod celery;
2+
pub mod jdbc;
3+
pub mod sqlalchemy;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use k8s_openapi::api::core::v1::EnvVar;
2+
use schemars::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
5+
use crate::builder::pod::{container::ContainerBuilder, env::env_var_from_secret};
6+
7+
/// TODO docs
8+
pub trait SQLAlchemyDatabaseConnection {
9+
/// TODO docs, e.g. on what are valid characters for unique_database_name
10+
fn sqlalchemy_connection_details(
11+
&self,
12+
unique_database_name: &str,
13+
) -> SQLAlchemyDatabaseConnectionDetails;
14+
}
15+
16+
pub struct SQLAlchemyDatabaseConnectionDetails {
17+
/// The connection URI, which can contain env variable templates, e.g.
18+
/// `postgresql+psycopg2://${METADATA_DATABASE_USERNAME}:${METADATA_DATABASE_PASSWORD}@airflow-postgresql:5432/airflow`
19+
/// or
20+
/// `<generic URI from the user>`.
21+
pub uri_template: String,
22+
23+
/// The [`EnvVar`]s the operator needs to mount into the created Pods.
24+
pub env_vars: Vec<EnvVar>,
25+
}
26+
27+
impl SQLAlchemyDatabaseConnectionDetails {
28+
pub fn add_to_container(&self, cb: &mut ContainerBuilder) {
29+
cb.add_env_vars(self.env_vars.iter());
30+
}
31+
}
32+
33+
/// TODO docs
34+
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
35+
#[serde(rename_all = "camelCase")]
36+
pub struct GenericSQLAlchemyDatabaseConnection {
37+
/// The name of the Secret that contains an `uri` key with the complete SQLAlchemy URI.
38+
pub uri_secret: String,
39+
}
40+
41+
impl SQLAlchemyDatabaseConnection for GenericSQLAlchemyDatabaseConnection {
42+
fn sqlalchemy_connection_details(
43+
&self,
44+
unique_database_name: &str,
45+
) -> SQLAlchemyDatabaseConnectionDetails {
46+
let uri_env_name = format!(
47+
"{upper}_DATABASE_URI",
48+
upper = unique_database_name.to_uppercase()
49+
);
50+
let uri_env_var = env_var_from_secret(&uri_env_name, &self.uri_secret, "uri");
51+
52+
SQLAlchemyDatabaseConnectionDetails {
53+
uri_template: format!("${{{uri_env_name}}}"),
54+
env_vars: vec![uri_env_var],
55+
}
56+
}
57+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::collections::BTreeMap;
2+
3+
use k8s_openapi::api::core::v1::EnvVar;
4+
5+
use crate::builder::pod::env::env_var_from_secret;
6+
7+
/// Returns the needed [`EnvVar`] mounts for username and password.
8+
///
9+
/// They will mount the specified Secret as env var into the Pod.
10+
pub fn username_and_password_envs(
11+
unique_database_name: &str,
12+
credentials_secret_name: &str,
13+
) -> (EnvVar, EnvVar) {
14+
let (username_env_name, password_env_name) =
15+
username_and_password_env_names(unique_database_name);
16+
(
17+
env_var_from_secret(username_env_name, credentials_secret_name, "username"),
18+
env_var_from_secret(password_env_name, credentials_secret_name, "password"),
19+
)
20+
}
21+
22+
pub fn username_and_password_env_names(unique_database_name: &str) -> (String, String) {
23+
let env_name_prefix = format!(
24+
"{upper}_DATABASE",
25+
upper = unique_database_name.to_uppercase()
26+
);
27+
(
28+
format!("{env_name_prefix}_USERNAME"),
29+
format!("{env_name_prefix}_PASSWORD"),
30+
)
31+
}
32+
33+
/// Returns
34+
///
35+
/// * If no params are defined: ""
36+
/// * If params are defined: "?key=value&foo=bar"
37+
pub fn connection_parameters_as_url_query_parameters(
38+
parameters: &BTreeMap<String, String>,
39+
) -> String {
40+
if parameters.is_empty() {
41+
String::new()
42+
} else {
43+
let parameters = parameters
44+
.iter()
45+
.map(|(k, v)| format!("{k}={v}"))
46+
.collect::<Vec<_>>()
47+
.join("&");
48+
format!("?{parameters}")
49+
}
50+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use snafu::Snafu;
2+
3+
pub mod client;
4+
mod helpers;
5+
pub mod server;
6+
#[cfg(test)]
7+
mod tests;
8+
9+
#[derive(Debug, Snafu)]
10+
pub enum Error {
11+
#[snafu(context(false), display("postgres error"))]
12+
Postgres { source: server::postgres::Error },
13+
14+
#[snafu(context(false), display("derby error"))]
15+
Derby { source: server::derby::Error },
16+
}
17+
18+
// /// TODO docs
19+
// pub trait CeleryDatabaseConnection {
20+
// /// TODO docs, e.g. on what are valid characters for unique_database_name
21+
// fn celery_connection_details(
22+
// &self,
23+
// unique_database_name: &str,
24+
// ) -> CeleryDatabaseConnectionDetails;
25+
// }
26+
27+
// pub struct CeleryDatabaseConnectionDetails {
28+
// /// The connection URI, which can contain env variable templates, e.g.
29+
// /// `redis://:redis@airflow-redis-master:6379/0`
30+
// pub uri_template: String,
31+
32+
// /// The [`EnvVar`]s the operator needs to mount into the created Pods.
33+
// pub env_vars: Vec<EnvVar>,
34+
// }
35+
36+
// impl CeleryDatabaseConnectionDetails {
37+
// pub fn add_to_container(&self, cb: &mut ContainerBuilder) {
38+
// cb.add_env_vars(self.env_vars.clone());
39+
// }
40+
// }
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use schemars::JsonSchema;
2+
use serde::{Deserialize, Serialize};
3+
use snafu::{ResultExt, Snafu};
4+
5+
use crate::crd::database::client::jdbc::{JDBCDatabaseConnection, JDBCDatabaseConnectionDetails};
6+
7+
#[derive(Debug, Snafu)]
8+
pub enum Error {
9+
#[snafu(display("failed to parse connection URL"))]
10+
ParseConnectionUrl { source: url::ParseError },
11+
}
12+
13+
/// TODO docs
14+
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct DerbyConnection {
17+
/// TODO docs, especially on default
18+
pub location: Option<String>,
19+
}
20+
21+
impl JDBCDatabaseConnection for DerbyConnection {
22+
fn jdbc_connection_details(
23+
&self,
24+
unique_database_name: &str,
25+
) -> Result<JDBCDatabaseConnectionDetails, crate::crd::database::Error> {
26+
let location = self
27+
.location
28+
.clone()
29+
.unwrap_or_else(|| format!("/tmp/derby/{unique_database_name}/derby.db"));
30+
let connection_uri = format!("jdbc:derby:{location}",);
31+
let connection_uri = connection_uri.parse().context(ParseConnectionUrlSnafu)?;
32+
33+
Ok(JDBCDatabaseConnectionDetails {
34+
driver: "org.apache.derby.jdbc.ClientDriver".to_owned(),
35+
connection_uri,
36+
username_env: None,
37+
password_env: None,
38+
})
39+
}
40+
}

0 commit comments

Comments
 (0)