Skip to content

Commit 62b1bad

Browse files
committed
Store cluster domain in separate KubernetesClusterInfo struct
1 parent b08ed8e commit 62b1bad

File tree

5 files changed

+102
-115
lines changed

5 files changed

+102
-115
lines changed

crates/stackable-operator/src/cli.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ use clap::Args;
115115
use product_config::ProductConfigManager;
116116
use snafu::{ResultExt, Snafu};
117117

118-
use crate::{logging::TracingTarget, namespace::WatchNamespace};
118+
use crate::{commons::networking::DomainName, logging::TracingTarget, namespace::WatchNamespace};
119119

120120
pub const AUTHOR: &str = "Stackable GmbH - [email protected]";
121121

@@ -179,7 +179,8 @@ pub enum Command<Run: Args = ProductOperatorRun> {
179179
/// common: ProductOperatorRun {
180180
/// product_config: ProductConfigPath::from("bar".as_ref()),
181181
/// watch_namespace: WatchNamespace::One("foobar".to_string()),
182-
/// tracing_target: TracingTarget::None
182+
/// tracing_target: TracingTarget::None,
183+
/// kubernetes_cluster_domain: None,
183184
/// },
184185
/// }));
185186
/// ```
@@ -205,12 +206,20 @@ pub struct ProductOperatorRun {
205206
/// Provides the path to a product-config file
206207
#[arg(long, short = 'p', value_name = "FILE", default_value = "", env)]
207208
pub product_config: ProductConfigPath,
209+
208210
/// Provides a specific namespace to watch (instead of watching all namespaces)
209211
#[arg(long, env, default_value = "")]
210212
pub watch_namespace: WatchNamespace,
213+
211214
/// Tracing log collector system
212215
#[arg(long, env, default_value_t, value_enum)]
213216
pub tracing_target: TracingTarget,
217+
218+
/// Kubernetes cluster domain, usually this is `cluster.local`.
219+
// We are not using a default value here, as operators will probably do an more advanced
220+
// auto-detection of the cluster domain in case it is not specified in the future.
221+
#[arg(long, env)]
222+
pub kubernetes_cluster_domain: Option<DomainName>,
214223
}
215224

216225
/// A path to a [`ProductConfigManager`] spec file
@@ -384,6 +393,7 @@ mod tests {
384393
product_config: ProductConfigPath::from("bar".as_ref()),
385394
watch_namespace: WatchNamespace::One("foo".to_string()),
386395
tracing_target: TracingTarget::None,
396+
kubernetes_cluster_domain: None,
387397
}
388398
);
389399

@@ -395,6 +405,7 @@ mod tests {
395405
product_config: ProductConfigPath::from("bar".as_ref()),
396406
watch_namespace: WatchNamespace::All,
397407
tracing_target: TracingTarget::None,
408+
kubernetes_cluster_domain: None,
398409
}
399410
);
400411

@@ -407,6 +418,7 @@ mod tests {
407418
product_config: ProductConfigPath::from("bar".as_ref()),
408419
watch_namespace: WatchNamespace::One("foo".to_string()),
409420
tracing_target: TracingTarget::None,
421+
kubernetes_cluster_domain: None,
410422
}
411423
);
412424
}

crates/stackable-operator/src/client.rs

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1-
use crate::commons::networking::DomainName;
2-
use crate::kvp::LabelSelectorExt;
3-
use crate::utils::cluster_domain::{self, retrieve_cluster_domain};
1+
use std::{
2+
convert::TryFrom,
3+
fmt::{Debug, Display},
4+
};
45

56
use either::Either;
67
use futures::StreamExt;
7-
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
8-
use k8s_openapi::{ClusterResourceScope, NamespaceResourceScope};
9-
use kube::api::{DeleteParams, ListParams, Patch, PatchParams, PostParams, Resource, ResourceExt};
10-
use kube::client::Client as KubeClient;
11-
use kube::core::Status;
12-
use kube::runtime::wait::delete::delete_and_finalize;
13-
use kube::runtime::{watcher, WatchStreamExt};
14-
use kube::{Api, Config};
15-
use serde::de::DeserializeOwned;
16-
use serde::Serialize;
8+
use k8s_openapi::{
9+
apimachinery::pkg::apis::meta::v1::LabelSelector, ClusterResourceScope, NamespaceResourceScope,
10+
};
11+
use kube::{
12+
api::{DeleteParams, ListParams, Patch, PatchParams, PostParams, Resource, ResourceExt},
13+
client::Client as KubeClient,
14+
core::Status,
15+
runtime::{wait::delete::delete_and_finalize, watcher, WatchStreamExt},
16+
Api, Config,
17+
};
18+
use serde::{de::DeserializeOwned, Serialize};
1719
use snafu::{OptionExt, ResultExt, Snafu};
18-
use std::convert::TryFrom;
19-
use std::fmt::{Debug, Display};
2020
use tracing::trace;
2121

22+
use crate::{
23+
cli::ProductOperatorRun, kvp::LabelSelectorExt, utils::cluster_info::KubernetesClusterInfo,
24+
};
25+
2226
pub type Result<T, E = Error> = std::result::Result<T, E>;
2327

2428
#[derive(Debug, Snafu)]
@@ -79,9 +83,6 @@ pub enum Error {
7983

8084
#[snafu(display("unable to create kubernetes client"))]
8185
CreateKubeClient { source: kube::Error },
82-
83-
#[snafu(display("unable to to resolve kubernetes cluster domain"))]
84-
ResolveKubernetesClusterDomain { source: cluster_domain::Error },
8586
}
8687

8788
/// This `Client` can be used to access Kubernetes.
@@ -94,15 +95,16 @@ pub struct Client {
9495
delete_params: DeleteParams,
9596
/// Default namespace as defined in the kubeconfig this client has been created from.
9697
pub default_namespace: String,
97-
pub kubernetes_cluster_domain: DomainName,
98+
99+
pub kubernetes_cluster_info: KubernetesClusterInfo,
98100
}
99101

100102
impl Client {
101103
pub fn new(
102104
client: KubeClient,
103105
field_manager: Option<String>,
104106
default_namespace: String,
105-
kubernetes_cluster_domain: DomainName,
107+
kubernetes_cluster_info: KubernetesClusterInfo,
106108
) -> Self {
107109
Client {
108110
client,
@@ -116,7 +118,7 @@ impl Client {
116118
},
117119
delete_params: DeleteParams::default(),
118120
default_namespace,
119-
kubernetes_cluster_domain,
121+
kubernetes_cluster_info,
120122
}
121123
}
122124

@@ -515,15 +517,18 @@ impl Client {
515517
///
516518
/// ```no_run
517519
/// use std::time::Duration;
520+
/// use clap::Parser;
518521
/// use tokio::time::error::Elapsed;
519522
/// use kube::runtime::watcher;
520523
/// use k8s_openapi::api::core::v1::Pod;
521-
/// use stackable_operator::client::{Client, initialize_operator};
524+
/// use stackable_operator::{cli::ProductOperatorRun, client::{Client, initialize_operator}};
522525
///
523526
/// #[tokio::main]
524527
/// async fn main(){
525528
///
526-
/// let client: Client = initialize_operator(None).await.expect("Unable to construct client.");
529+
/// // Parse CLI arguments with Opts::parse() instead
530+
/// let cli_opts = ProductOperatorRun::parse_from(["run"]);
531+
/// let client: Client = initialize_operator(&cli_opts, None).await.expect("Unable to construct client.");
527532
/// let watcher_config: watcher::Config =
528533
/// watcher::Config::default().fields(&format!("metadata.name=nonexistent-pod"));
529534
///
@@ -630,39 +635,49 @@ where
630635
}
631636
}
632637

633-
pub async fn initialize_operator(field_manager: Option<String>) -> Result<Client> {
638+
pub async fn initialize_operator(
639+
cli_opts: &ProductOperatorRun,
640+
field_manager: Option<String>,
641+
) -> Result<Client> {
634642
let kubeconfig: Config = kube::Config::infer()
635643
.await
636644
.map_err(kube::Error::InferConfig)
637645
.context(InferKubeConfigSnafu)?;
638646
let default_namespace = kubeconfig.default_namespace.clone();
639647
let client = kube::Client::try_from(kubeconfig).context(CreateKubeClientSnafu)?;
640-
let cluster_domain = retrieve_cluster_domain().context(ResolveKubernetesClusterDomainSnafu)?;
648+
let cluster_info = KubernetesClusterInfo::new(cli_opts);
641649

642650
Ok(Client::new(
643651
client,
644652
field_manager,
645653
default_namespace,
646-
cluster_domain,
654+
cluster_info,
647655
))
648656
}
649657

650658
#[cfg(test)]
651659
mod tests {
660+
use std::{collections::BTreeMap, time::Duration};
661+
662+
use clap::Parser;
652663
use futures::StreamExt;
653-
use k8s_openapi::api::core::v1::{Container, Pod, PodSpec};
654-
use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
655-
use kube::api::{ObjectMeta, PostParams, ResourceExt};
656-
use kube::runtime::watcher;
657-
use kube::runtime::watcher::Event;
658-
use std::collections::BTreeMap;
659-
use std::time::Duration;
664+
use k8s_openapi::{
665+
api::core::v1::{Container, Pod, PodSpec},
666+
apimachinery::pkg::apis::meta::v1::LabelSelector,
667+
};
668+
use kube::{
669+
api::{ObjectMeta, PostParams, ResourceExt},
670+
runtime::watcher::{self, Event},
671+
};
660672
use tokio::time::error::Elapsed;
661673

674+
use crate::cli::ProductOperatorRun;
675+
662676
#[tokio::test]
663677
#[ignore = "Tests depending on Kubernetes are not ran by default"]
664678
async fn k8s_test_wait_created() {
665-
let client = super::initialize_operator(None)
679+
let cli_opts = ProductOperatorRun::parse_from(["run"]);
680+
let client = super::initialize_operator(&cli_opts, None)
666681
.await
667682
.expect("KUBECONFIG variable must be configured.");
668683

@@ -740,7 +755,8 @@ mod tests {
740755
#[tokio::test]
741756
#[ignore = "Tests depending on Kubernetes are not ran by default"]
742757
async fn k8s_test_wait_created_timeout() {
743-
let client = super::initialize_operator(None)
758+
let cli_opts = ProductOperatorRun::parse_from(["run"]);
759+
let client = super::initialize_operator(&cli_opts, None)
744760
.await
745761
.expect("KUBECONFIG variable must be configured.");
746762

@@ -760,7 +776,8 @@ mod tests {
760776
#[tokio::test]
761777
#[ignore = "Tests depending on Kubernetes are not ran by default"]
762778
async fn k8s_test_list_with_label_selector() {
763-
let client = super::initialize_operator(None)
779+
let cli_opts = ProductOperatorRun::parse_from(["run"]);
780+
let client = super::initialize_operator(&cli_opts, None)
764781
.await
765782
.expect("KUBECONFIG variable must be configured.");
766783

crates/stackable-operator/src/utils/cluster_domain.rs

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::str::FromStr;
2+
3+
use crate::{cli::ProductOperatorRun, commons::networking::DomainName};
4+
5+
const KUBERNETES_CLUSTER_DOMAIN_DEFAULT: &str = "cluster.local";
6+
7+
#[derive(Debug, Clone)]
8+
pub struct KubernetesClusterInfo {
9+
pub cluster_domain: DomainName,
10+
}
11+
12+
impl KubernetesClusterInfo {
13+
pub fn new(cli_opts: &ProductOperatorRun) -> Self {
14+
let cluster_domain = match &cli_opts.kubernetes_cluster_domain {
15+
Some(cluster_domain) => {
16+
tracing::info!(%cluster_domain, "Using configured Kubernetes cluster domain");
17+
18+
cluster_domain.clone()
19+
}
20+
None => {
21+
// TODO(sbernauer): Do some sort of advanced auto-detection, see https://github.com/stackabletech/issues/issues/436.
22+
// There have been attempts of parsing the `/etc/resolv.conf`, but they have been
23+
// reverted. Please read on the linked Issue for details.
24+
let cluster_domain = DomainName::from_str(KUBERNETES_CLUSTER_DOMAIN_DEFAULT)
25+
.expect("KUBERNETES_CLUSTER_DOMAIN_DEFAULT constant must a valid domain");
26+
tracing::info!(%cluster_domain, "Defaulting Kubernetes cluster domain as it has not been configured");
27+
28+
cluster_domain
29+
}
30+
};
31+
32+
Self { cluster_domain }
33+
}
34+
}

crates/stackable-operator/src/utils/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod bash;
2-
pub mod cluster_domain;
2+
pub mod cluster_info;
33
pub mod crds;
44
pub mod logging;
55
mod option;

0 commit comments

Comments
 (0)