diff --git a/rust/stackable-cockpit/src/platform/cluster/resource_request.rs b/rust/stackable-cockpit/src/platform/cluster/resource_request.rs index b97f9371..6b3adfda 100644 --- a/rust/stackable-cockpit/src/platform/cluster/resource_request.rs +++ b/rust/stackable-cockpit/src/platform/cluster/resource_request.rs @@ -91,12 +91,8 @@ impl ResourceRequests { /// Validates the struct [`ResourceRequests`] by comparing the required /// resources to the available ones in the current cluster. `object_name` /// should be `stack` or `demo`. - pub async fn validate_cluster_size(&self, object_name: &str) -> Result<()> { - let kube_client = Client::new().await.context(KubeClientCreateSnafu)?; - let cluster_info = kube_client - .get_cluster_info() - .await - .context(ClusterInfoSnafu)?; + pub async fn validate_cluster_size(&self, client: &Client, object_name: &str) -> Result<()> { + let cluster_info = client.get_cluster_info().await.context(ClusterInfoSnafu)?; let stack_cpu = CpuQuantity::try_from(&self.cpu).context(ParseCpuResourceRequirementsSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/credentials.rs b/rust/stackable-cockpit/src/platform/credentials.rs index 67bb9cb0..abe35ae4 100644 --- a/rust/stackable-cockpit/src/platform/credentials.rs +++ b/rust/stackable-cockpit/src/platform/credentials.rs @@ -4,7 +4,7 @@ use kube::{core::DynamicObject, ResourceExt}; use serde::Serialize; use snafu::{OptionExt, ResultExt, Snafu}; -use crate::utils::k8s; +use crate::utils::k8s::{self, Client}; pub type Result = std::result::Result; @@ -34,7 +34,7 @@ impl Display for Credentials { /// and/or `password_key` are not found or the product does not provide /// any credentials. pub async fn get( - kube_client: &k8s::Client, + client: &Client, product_name: &str, stacklet: &DynamicObject, ) -> Result> { @@ -56,7 +56,7 @@ pub async fn get( .as_str() .context(NoSecretSnafu)?; - kube_client + client .get_credentials_from_secret( secret_name, &stacklet.namespace().unwrap(), @@ -71,7 +71,7 @@ pub async fn get( .as_str() .context(NoSecretSnafu)?; - kube_client + client .get_credentials_from_secret( secret_name, &stacklet.namespace().unwrap(), diff --git a/rust/stackable-cockpit/src/platform/demo/spec.rs b/rust/stackable-cockpit/src/platform/demo/spec.rs index 8c3fbe5b..68741e87 100644 --- a/rust/stackable-cockpit/src/platform/demo/spec.rs +++ b/rust/stackable-cockpit/src/platform/demo/spec.rs @@ -14,10 +14,13 @@ use crate::{ release::ReleaseList, stack::{self, StackInstallParameters, StackList}, }, - utils::params::{ - IntoParameters, IntoParametersError, Parameter, RawParameter, RawParameterParseError, + utils::{ + k8s::Client, + params::{ + IntoParameters, IntoParametersError, Parameter, RawParameter, RawParameterParseError, + }, }, - xfer::{self, Client}, + xfer, }; pub type RawDemoParameterParseError = RawParameterParseError; @@ -94,7 +97,11 @@ impl DemoSpec { /// - Does the demo support to be installed in the requested namespace? /// - Does the cluster have enough resources available to run this demo? #[instrument(skip_all)] - pub async fn check_prerequisites(&self, product_namespace: &str) -> Result<(), Error> { + pub async fn check_prerequisites( + &self, + client: &Client, + product_namespace: &str, + ) -> Result<(), Error> { debug!("Checking prerequisites before installing demo"); // Returns an error if the demo doesn't support to be installed in the @@ -109,7 +116,10 @@ impl DemoSpec { // Checks if the available cluster resources are sufficient to deploy // the demo. if let Some(resource_requests) = &self.resource_requests { - if let Err(err) = resource_requests.validate_cluster_size("demo").await { + if let Err(err) = resource_requests + .validate_cluster_size(client, "demo") + .await + { match err { ResourceRequestsError::ValidationErrors { errors } => { for error in errors { @@ -129,7 +139,8 @@ impl DemoSpec { stack_list: StackList, release_list: ReleaseList, install_parameters: DemoInstallParameters, - transfer_client: &Client, + client: &Client, + transfer_client: &xfer::Client, ) -> Result<(), Error> { // Get the stack spec based on the name defined in the demo spec let stack = stack_list.get(&self.stack).context(NoSuchStackSnafu { @@ -137,7 +148,7 @@ impl DemoSpec { })?; // Check demo prerequisites - self.check_prerequisites(&install_parameters.product_namespace) + self.check_prerequisites(client, &install_parameters.product_namespace) .await?; let stack_install_parameters = StackInstallParameters { @@ -151,12 +162,17 @@ impl DemoSpec { }; stack - .install(release_list, stack_install_parameters, transfer_client) + .install( + release_list, + stack_install_parameters, + client, + transfer_client, + ) .await .context(InstallStackSnafu)?; // Install demo manifests - self.prepare_manifests(install_parameters, transfer_client) + self.prepare_manifests(install_parameters, client, transfer_client) .await } @@ -164,6 +180,7 @@ impl DemoSpec { async fn prepare_manifests( &self, install_params: DemoInstallParameters, + client: &Client, transfer_client: &xfer::Client, ) -> Result<(), Error> { info!("Installing demo manifests"); @@ -179,6 +196,7 @@ impl DemoSpec { ¶ms, &install_params.product_namespace, install_params.labels, + client, transfer_client, ) .await diff --git a/rust/stackable-cockpit/src/platform/manifests.rs b/rust/stackable-cockpit/src/platform/manifests.rs index 8a839434..882533e0 100644 --- a/rust/stackable-cockpit/src/platform/manifests.rs +++ b/rust/stackable-cockpit/src/platform/manifests.rs @@ -8,7 +8,7 @@ use crate::{ common::manifest::ManifestSpec, helm, utils::{ - k8s, + k8s::{self, Client}, path::{IntoPathOrUrl, PathOrUrlParseError}, }, xfer::{ @@ -61,7 +61,7 @@ pub enum Error { } pub trait InstallManifestsExt { - // TODO (Techassi): This step shouldn't care about templating the manifests nor fecthing them from remote + // TODO (Techassi): This step shouldn't care about templating the manifests nor fetching them from remote #[instrument(skip_all)] #[allow(async_fn_in_trait)] async fn install_manifests( @@ -69,12 +69,11 @@ pub trait InstallManifestsExt { parameters: &HashMap, product_namespace: &str, labels: Labels, + client: &Client, transfer_client: &xfer::Client, ) -> Result<(), Error> { debug!("Installing demo / stack manifests"); - let kube_client = k8s::Client::new().await.context(CreateKubeClientSnafu)?; - for manifest in manifests { match manifest { ManifestSpec::HelmChart(helm_file) => { @@ -137,7 +136,7 @@ pub trait InstallManifestsExt { .await .context(FileTransferSnafu)?; - kube_client + client .deploy_manifests(&manifests, product_namespace, labels.clone()) .await .context(DeployManifestSnafu)? diff --git a/rust/stackable-cockpit/src/platform/namespace.rs b/rust/stackable-cockpit/src/platform/namespace.rs index 46e632a0..df29f404 100644 --- a/rust/stackable-cockpit/src/platform/namespace.rs +++ b/rust/stackable-cockpit/src/platform/namespace.rs @@ -1,6 +1,6 @@ -use snafu::{ResultExt, Snafu}; +use snafu::Snafu; -use crate::utils::k8s; +use crate::utils::k8s::{self, Client}; #[derive(Debug, Snafu)] pub enum Error { @@ -13,9 +13,7 @@ pub enum Error { /// Creates a namespace with `name` if needed (not already present in the /// cluster). -pub async fn create_if_needed(name: String) -> Result<(), Error> { - let client = k8s::Client::new().await.context(KubeClientCreateSnafu)?; - +pub async fn create_if_needed(client: &Client, name: String) -> Result<(), Error> { client .create_namespace_if_needed(name) .await diff --git a/rust/stackable-cockpit/src/platform/service.rs b/rust/stackable-cockpit/src/platform/service.rs index 402fa05e..4be932c9 100644 --- a/rust/stackable-cockpit/src/platform/service.rs +++ b/rust/stackable-cockpit/src/platform/service.rs @@ -13,7 +13,7 @@ use kube::{api::ListParams, ResourceExt}; use snafu::{OptionExt, ResultExt, Snafu}; use tracing::{debug, warn}; -use crate::utils::k8s::{self, ListParamsExt}; +use crate::utils::k8s::{self, Client, ListParamsExt}; #[derive(Debug, Snafu)] pub enum Error { @@ -40,7 +40,7 @@ pub enum Error { } pub async fn get_endpoints( - kube_client: &k8s::Client, + client: &Client, product_name: &str, object_name: &str, object_namespace: &str, @@ -48,7 +48,7 @@ pub async fn get_endpoints( let list_params = ListParams::from_product(product_name, Some(object_name), k8s::ProductLabel::Name); - let listeners = kube_client + let listeners = client .list_listeners(Some(object_namespace), &list_params) .await; let listeners = match listeners { @@ -92,13 +92,13 @@ pub async fn get_endpoints( return Ok(endpoints); } - let services = kube_client + let services = client .list_services(Some(object_namespace), &list_params) .await .context(KubeClientFetchSnafu)?; for service in services { - match get_endpoint_urls(kube_client, &service, object_name).await { + match get_endpoint_urls(client, &service, object_name).await { Ok(urls) => endpoints.extend(urls), Err(err) => warn!( "Failed to get endpoint_urls of service {service_name}: {err}", @@ -111,7 +111,7 @@ pub async fn get_endpoints( } pub async fn get_endpoint_urls( - kube_client: &k8s::Client, + client: &Client, service: &Service, referenced_object_name: &str, ) -> Result, Error> { @@ -128,7 +128,7 @@ pub async fn get_endpoint_urls( let endpoints = match service_spec.type_.as_deref() { Some("NodePort") => { get_endpoint_urls_for_nodeport( - kube_client, + client, &service_name, service_spec, &service_namespace, @@ -152,13 +152,13 @@ pub async fn get_endpoint_urls( } pub async fn get_endpoint_urls_for_nodeport( - kube_client: &k8s::Client, + client: &Client, service_name: &str, service_spec: &ServiceSpec, service_namespace: &str, referenced_object_name: &str, ) -> Result, Error> { - let endpoints = kube_client + let endpoints = client .get_endpoints(service_namespace, service_name) .await .context(KubeClientFetchSnafu)?; @@ -191,7 +191,7 @@ pub async fn get_endpoint_urls_for_nodeport( } }; - let node_ip = get_node_ip(kube_client, node_name).await?; + let node_ip = get_node_ip(client, node_name).await?; let mut endpoints = IndexMap::new(); for service_port in service_spec.ports.iter().flatten() { @@ -265,8 +265,8 @@ pub async fn get_endpoint_urls_for_loadbalancer( Ok(endpoints) } -async fn get_node_ip(kube_client: &k8s::Client, node_name: &str) -> Result { - let node_name_ip_mapping = get_node_name_ip_mapping(kube_client).await?; +async fn get_node_ip(client: &Client, node_name: &str) -> Result { + let node_name_ip_mapping = get_node_name_ip_mapping(client).await?; match node_name_ip_mapping.get(node_name) { Some(node_ip) => Ok(node_ip.to_string()), @@ -276,13 +276,8 @@ async fn get_node_ip(kube_client: &k8s::Client, node_name: &str) -> Result Result, Error> { - let nodes = kube_client - .list_nodes() - .await - .context(KubeClientFetchSnafu)?; +async fn get_node_name_ip_mapping(client: &Client) -> Result, Error> { + let nodes = client.list_nodes().await.context(KubeClientFetchSnafu)?; let mut result = HashMap::new(); for node in nodes { diff --git a/rust/stackable-cockpit/src/platform/stack/spec.rs b/rust/stackable-cockpit/src/platform/stack/spec.rs index 1ed3e9b5..499c08b0 100644 --- a/rust/stackable-cockpit/src/platform/stack/spec.rs +++ b/rust/stackable-cockpit/src/platform/stack/spec.rs @@ -13,8 +13,11 @@ use crate::{ namespace, release, stack::StackInstallParameters, }, - utils::params::{ - IntoParameters, IntoParametersError, Parameter, RawParameter, RawParameterParseError, + utils::{ + k8s::Client, + params::{ + IntoParameters, IntoParametersError, Parameter, RawParameter, RawParameterParseError, + }, }, xfer, }; @@ -106,7 +109,11 @@ impl StackSpec { /// - Does the stack support to be installed in the requested namespace? /// - Does the cluster have enough resources available to run this stack? #[instrument(skip_all)] - pub async fn check_prerequisites(&self, product_namespace: &str) -> Result<(), Error> { + pub async fn check_prerequisites( + &self, + client: &Client, + product_namespace: &str, + ) -> Result<(), Error> { debug!("Checking prerequisites before installing stack"); // Returns an error if the stack doesn't support to be installed in the @@ -123,7 +130,10 @@ impl StackSpec { // Checks if the available cluster resources are sufficient to deploy // the demo. if let Some(resource_requests) = &self.resource_requests { - if let Err(err) = resource_requests.validate_cluster_size("stack").await { + if let Err(err) = resource_requests + .validate_cluster_size(client, "stack") + .await + { match err { ResourceRequestsError::ValidationErrors { errors } => { for error in errors { @@ -139,20 +149,21 @@ impl StackSpec { } // TODO (Techassi): Can we get rid of the release list and just use the release spec instead - #[instrument(skip(self, release_list, transfer_client))] + #[instrument(skip(self, release_list, client, transfer_client))] pub async fn install( &self, release_list: release::ReleaseList, install_parameters: StackInstallParameters, + client: &Client, transfer_client: &xfer::Client, ) -> Result<(), Error> { // First, we check if the prerequisites are met - self.check_prerequisites(&install_parameters.product_namespace) + self.check_prerequisites(client, &install_parameters.product_namespace) .await?; // Second, we install the release if not opted out if !install_parameters.skip_release { - namespace::create_if_needed(install_parameters.operator_namespace.clone()) + namespace::create_if_needed(client, install_parameters.operator_namespace.clone()) .await .context(CreateNamespaceSnafu { namespace: install_parameters.operator_namespace.clone(), @@ -167,14 +178,14 @@ impl StackSpec { } // Next, create the product namespace if needed - namespace::create_if_needed(install_parameters.product_namespace.clone()) + namespace::create_if_needed(client, install_parameters.product_namespace.clone()) .await .context(CreateNamespaceSnafu { namespace: install_parameters.product_namespace.clone(), })?; // Finally install the stack manifests - self.prepare_manifests(install_parameters, transfer_client) + self.prepare_manifests(install_parameters, client, transfer_client) .await } @@ -205,6 +216,7 @@ impl StackSpec { pub async fn prepare_manifests( &self, install_params: StackInstallParameters, + client: &Client, transfer_client: &xfer::Client, ) -> Result<(), Error> { info!("Installing stack manifests"); @@ -220,6 +232,7 @@ impl StackSpec { ¶meters, &install_params.product_namespace, install_params.labels, + client, transfer_client, ) .await diff --git a/rust/stackable-cockpit/src/platform/stacklet/grafana.rs b/rust/stackable-cockpit/src/platform/stacklet/grafana.rs index e77eef8b..941035d8 100644 --- a/rust/stackable-cockpit/src/platform/stacklet/grafana.rs +++ b/rust/stackable-cockpit/src/platform/stacklet/grafana.rs @@ -9,21 +9,18 @@ use crate::{ utils::k8s::{Client, ListParamsExt, ProductLabel}, }; -pub(super) async fn list( - kube_client: &Client, - namespace: Option<&str>, -) -> Result, Error> { +pub(super) async fn list(client: &Client, namespace: Option<&str>) -> Result, Error> { let mut stacklets = Vec::new(); let params = ListParams::from_product("grafana", None, ProductLabel::Name); - let services = kube_client + let services = client .list_services(namespace, ¶ms) .await .context(KubeClientFetchSnafu)?; for service in services { let service_name = service.name_any(); - let endpoints = get_endpoint_urls(kube_client, &service, &service_name) + let endpoints = get_endpoint_urls(client, &service, &service_name) .await .context(ServiceFetchSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/stacklet/minio.rs b/rust/stackable-cockpit/src/platform/stacklet/minio.rs index 749fcc07..e7e77a40 100644 --- a/rust/stackable-cockpit/src/platform/stacklet/minio.rs +++ b/rust/stackable-cockpit/src/platform/stacklet/minio.rs @@ -9,15 +9,12 @@ use crate::{ utils::k8s::Client, }; -pub(super) async fn list( - kube_client: &Client, - namespace: Option<&str>, -) -> Result, Error> { +pub(super) async fn list(client: &Client, namespace: Option<&str>) -> Result, Error> { let mut stacklets = Vec::new(); // The helm-chart uses `app` instead of `app.kubernetes.io/app`, so we can't use `ListParams::from_product` here let params = ListParams::default().labels("app=minio,app.kubernetes.io/managed-by=Helm"); - let services = kube_client + let services = client .list_services(namespace, ¶ms) .await .context(KubeClientFetchSnafu)?; @@ -28,7 +25,7 @@ pub(super) async fn list( for service in console_services { let service_name = service.name_any(); - let endpoints = get_endpoint_urls(kube_client, service, &service_name) + let endpoints = get_endpoint_urls(client, service, &service_name) .await .context(ServiceFetchSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/stacklet/mod.rs b/rust/stackable-cockpit/src/platform/stacklet/mod.rs index fbd8dcb3..f8f35490 100644 --- a/rust/stackable-cockpit/src/platform/stacklet/mod.rs +++ b/rust/stackable-cockpit/src/platform/stacklet/mod.rs @@ -12,7 +12,7 @@ use crate::{ constants::PRODUCT_NAMES, platform::{credentials, service}, utils::{ - k8s::{self, ConditionsExt}, + k8s::{self, Client, ConditionsExt}, string::Casing, }, }; @@ -65,27 +65,27 @@ pub enum Error { /// namespaces are returned. If `namespace` is [`Some`], only stacklets installed /// in the specified namespace are returned. The `options` allow further /// customization of the returned information. -pub async fn list_stacklets(namespace: Option<&str>) -> Result, Error> { - let kube_client = k8s::Client::new().await.context(KubeClientCreateSnafu)?; - - let mut stacklets = list_stackable_stacklets(&kube_client, namespace).await?; - stacklets.extend(grafana::list(&kube_client, namespace).await?); - stacklets.extend(minio::list(&kube_client, namespace).await?); - stacklets.extend(opensearch::list(&kube_client, namespace).await?); - stacklets.extend(prometheus::list(&kube_client, namespace).await?); +pub async fn list_stacklets( + client: &Client, + namespace: Option<&str>, +) -> Result, Error> { + let mut stacklets = list_stackable_stacklets(client, namespace).await?; + stacklets.extend(grafana::list(client, namespace).await?); + stacklets.extend(minio::list(client, namespace).await?); + stacklets.extend(opensearch::list(client, namespace).await?); + stacklets.extend(prometheus::list(client, namespace).await?); Ok(stacklets) } pub async fn get_credentials_for_product( + client: &Client, namespace: &str, object_name: &str, product_name: &str, ) -> Result, Error> { - let kube_client = k8s::Client::new().await.context(KubeClientCreateSnafu)?; - let product_gvk = gvk_from_product_name(product_name); - let product_cluster = match kube_client + let product_cluster = match client .get_namespaced_object(namespace, object_name, &product_gvk) .await .context(KubeClientFetchSnafu)? @@ -99,7 +99,7 @@ pub async fn get_credentials_for_product( } }; - let credentials = match credentials::get(&kube_client, product_name, &product_cluster).await { + let credentials = match credentials::get(client, product_name, &product_cluster).await { Ok(credentials) => credentials, Err(credentials::Error::NoSecret) => None, Err(credentials::Error::KubeClientFetch { source }) => { @@ -111,14 +111,14 @@ pub async fn get_credentials_for_product( } async fn list_stackable_stacklets( - kube_client: &k8s::Client, + client: &Client, namespace: Option<&str>, ) -> Result, Error> { let product_list = build_products_gvk_list(PRODUCT_NAMES); let mut stacklets = Vec::new(); for (product_name, product_gvk) in product_list { - let objects = match kube_client + let objects = match client .list_objects(&product_gvk, namespace) .await .context(KubeClientFetchSnafu)? @@ -148,7 +148,7 @@ async fn list_stackable_stacklets( }; let endpoints = - service::get_endpoints(kube_client, product_name, &object_name, &object_namespace) + service::get_endpoints(client, product_name, &object_name, &object_namespace) .await .context(ServiceFetchSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/stacklet/opensearch.rs b/rust/stackable-cockpit/src/platform/stacklet/opensearch.rs index 5a65ec49..2e6f4b86 100644 --- a/rust/stackable-cockpit/src/platform/stacklet/opensearch.rs +++ b/rust/stackable-cockpit/src/platform/stacklet/opensearch.rs @@ -9,21 +9,18 @@ use crate::{ utils::k8s::{Client, ListParamsExt, ProductLabel}, }; -pub(super) async fn list( - kube_client: &Client, - namespace: Option<&str>, -) -> Result, Error> { +pub(super) async fn list(client: &Client, namespace: Option<&str>) -> Result, Error> { let mut stacklets = Vec::new(); let params = ListParams::from_product("opensearch-dashboards", None, ProductLabel::Name); - let services = kube_client + let services = client .list_services(namespace, ¶ms) .await .context(KubeClientFetchSnafu)?; for service in services { let service_name = service.name_any(); - let endpoints = get_endpoint_urls(kube_client, &service, &service_name) + let endpoints = get_endpoint_urls(client, &service, &service_name) .await .context(ServiceFetchSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/stacklet/prometheus.rs b/rust/stackable-cockpit/src/platform/stacklet/prometheus.rs index cd8a2560..6611e292 100644 --- a/rust/stackable-cockpit/src/platform/stacklet/prometheus.rs +++ b/rust/stackable-cockpit/src/platform/stacklet/prometheus.rs @@ -9,22 +9,19 @@ use crate::{ utils::k8s::Client, }; -pub(super) async fn list( - kube_client: &Client, - namespace: Option<&str>, -) -> Result, Error> { +pub(super) async fn list(client: &Client, namespace: Option<&str>) -> Result, Error> { let mut stacklets = Vec::new(); // The helm-chart uses `app` instead of `app.kubernetes.io/app`, so we can't use `ListParams::from_product` here let params = ListParams::default().labels("app=kube-prometheus-stack-prometheus"); - let services = kube_client + let services = client .list_services(namespace, ¶ms) .await .context(KubeClientFetchSnafu)?; for service in services { let service_name = service.name_any(); - let endpoints = get_endpoint_urls(kube_client, &service, &service_name) + let endpoints = get_endpoint_urls(client, &service, &service_name) .await .context(ServiceFetchSnafu)?; diff --git a/rust/stackable-cockpitd/src/handlers/stacklets.rs b/rust/stackable-cockpitd/src/handlers/stacklets.rs index c5b8bcc3..7a6c8231 100644 --- a/rust/stackable-cockpitd/src/handlers/stacklets.rs +++ b/rust/stackable-cockpitd/src/handlers/stacklets.rs @@ -1,5 +1,5 @@ use axum::{routing::get, Json, Router}; -use stackable_cockpit::platform; +use stackable_cockpit::{platform, utils::k8s::Client}; pub use stackable_cockpit::platform::stacklet::Stacklet; @@ -9,9 +9,16 @@ pub fn router() -> Router { } /// Retrieves all stacklets. +// TODO: Add proper error handling #[utoipa::path(get, path = "/stacklets", responses( (status = 200, body = Vec), ))] pub async fn get_stacklets() -> Json> { - Json(platform::stacklet::list_stacklets(None).await.unwrap()) + let client = Client::new().await.expect("failed to construct k8s client"); + + Json( + platform::stacklet::list_stacklets(&client, None) + .await + .expect("failed to list stacklets"), + ) } diff --git a/rust/stackablectl/CHANGELOG.md b/rust/stackablectl/CHANGELOG.md index 4633be61..7b5cc03d 100644 --- a/rust/stackablectl/CHANGELOG.md +++ b/rust/stackablectl/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Fixed +- Avoid unnecessary `k8s::Client` creations ([#295]). - Re-run GVK discovery after resolution failure ([#294]). ## [24.3.3] - 2024-05-13 @@ -14,6 +15,7 @@ All notable changes to this project will be documented in this file. [#238]: https://github.com/stackabletech/stackable-cockpit/pull/238 [#294]: https://github.com/stackabletech/stackable-cockpit/pull/294 +[#295]: https://github.com/stackabletech/stackable-cockpit/pull/295 ## [24.3.2] - 2024-04-25 diff --git a/rust/stackablectl/src/cmds/demo.rs b/rust/stackablectl/src/cmds/demo.rs index 3f161284..023a6b5a 100644 --- a/rust/stackablectl/src/cmds/demo.rs +++ b/rust/stackablectl/src/cmds/demo.rs @@ -14,8 +14,11 @@ use stackable_cockpit::{ demo::{self, DemoInstallParameters}, release, stack, }, - utils::path::PathOrUrlParseError, - xfer::{cache::Cache, Client}, + utils::{ + k8s::{self, Client}, + path::PathOrUrlParseError, + }, + xfer::{self, cache::Cache}, }; use crate::{ @@ -140,6 +143,9 @@ pub enum CmdError { #[snafu(display("failed to build labels for demo resources"))] BuildLabels { source: LabelError }, + + #[snafu(display("failed to create Kubernetes client"))] + KubeClientCreate { source: k8s::Error }, } impl DemoArgs { @@ -147,7 +153,7 @@ impl DemoArgs { pub async fn run(&self, cli: &Cli, cache: Cache) -> Result { debug!("Handle demo args"); - let transfer_client = Client::new_with(cache); + let transfer_client = xfer::Client::new_with(cache); // Build demo list based on the (default) remote demo file, and additional files provided by the // STACKABLE_DEMO_FILES env variable or the --demo-files CLI argument. @@ -266,12 +272,12 @@ async fn describe_cmd( } /// Install a specific demo -#[instrument(skip(list))] +#[instrument(skip(list, transfer_client))] async fn install_cmd( args: &DemoInstallArgs, cli: &Cli, list: demo::List, - transfer_client: &Client, + transfer_client: &xfer::Client, ) -> Result { info!("Installing demo {}", args.demo_name); @@ -299,6 +305,8 @@ async fn install_cmd( .await .context(InstallClusterSnafu)?; + let client = Client::new().await.context(KubeClientCreateSnafu)?; + // Construct labels which get attached to all dynamic objects which // are part of the demo and stack. let labels = Labels::try_from([ @@ -327,6 +335,7 @@ async fn install_cmd( stack_list, release_list, install_parameters, + &client, transfer_client, ) .await diff --git a/rust/stackablectl/src/cmds/operator.rs b/rust/stackablectl/src/cmds/operator.rs index 80b31631..28ca2cad 100644 --- a/rust/stackablectl/src/cmds/operator.rs +++ b/rust/stackablectl/src/cmds/operator.rs @@ -17,7 +17,10 @@ use stackable_cockpit::{ }, helm::{self, Release, Repository}, platform::{namespace, operator}, - utils, + utils::{ + self, + k8s::{self, Client}, + }, }; use crate::{ @@ -142,6 +145,9 @@ pub enum CmdError { #[snafu(display("failed to serialize JSON output"))] SerializeJsonOutput { source: serde_json::Error }, + #[snafu(display("failed to create Kubernetes client"))] + KubeClientCreate { source: k8s::Error }, + #[snafu(display("failed to create namespace '{namespace}'"))] NamespaceCreate { source: namespace::Error, @@ -290,7 +296,9 @@ async fn install_cmd(args: &OperatorInstallArgs, cli: &Cli) -> Result Result { debug!("Handle release args"); - let transfer_client = Client::new_with(cache); + let transfer_client = xfer::Client::new_with(cache); let files = cli.get_release_files().context(PathOrUrlParseSnafu)?; let release_list = release::ReleaseList::build(&files, &transfer_client) .await @@ -276,8 +282,10 @@ async fn install_cmd( .await .context(CommonClusterArgsSnafu)?; + let client = Client::new().await.context(KubeClientCreateSnafu)?; + // Create operator namespace if needed - namespace::create_if_needed(args.operator_namespace.clone()) + namespace::create_if_needed(&client, args.operator_namespace.clone()) .await .context(NamespaceCreateSnafu { namespace: args.operator_namespace.clone(), diff --git a/rust/stackablectl/src/cmds/stack.rs b/rust/stackablectl/src/cmds/stack.rs index 6090f8b2..0f659261 100644 --- a/rust/stackablectl/src/cmds/stack.rs +++ b/rust/stackablectl/src/cmds/stack.rs @@ -14,8 +14,11 @@ use stackable_cockpit::{ release, stack::{self, StackInstallParameters}, }, - utils::path::PathOrUrlParseError, - xfer::{cache::Cache, Client}, + utils::{ + k8s::{self, Client}, + path::PathOrUrlParseError, + }, + xfer::{self, cache::Cache}, }; use crate::{ @@ -127,13 +130,16 @@ pub enum CmdError { #[snafu(display("failed to build labels for stack resources"))] BuildLabels { source: LabelError }, + + #[snafu(display("failed to create Kubernetes client"))] + KubeClientCreate { source: k8s::Error }, } impl StackArgs { pub async fn run(&self, cli: &Cli, cache: Cache) -> Result { debug!("Handle stack args"); - let transfer_client = Client::new_with(cache); + let transfer_client = xfer::Client::new_with(cache); let files = cli.get_stack_files().context(PathOrUrlParseSnafu)?; let stack_list = stack::StackList::build(&files, &transfer_client) .await @@ -266,7 +272,7 @@ async fn install_cmd( args: &StackInstallArgs, cli: &Cli, stack_list: stack::StackList, - transfer_client: &Client, + transfer_client: &xfer::Client, ) -> Result { info!("Installing stack {}", args.stack_name); @@ -285,6 +291,8 @@ async fn install_cmd( .await .context(InstallClusterSnafu)?; + let client = Client::new().await.context(KubeClientCreateSnafu)?; + // Construct labels which get attached to all dynamic objects which // are part of the stack. let labels = Labels::try_from([ @@ -305,7 +313,7 @@ async fn install_cmd( }; stack_spec - .install(release_list, install_parameters, transfer_client) + .install(release_list, install_parameters, &client, transfer_client) .await .context(InstallStackSnafu { stack_name: args.stack_name.clone(), diff --git a/rust/stackablectl/src/cmds/stacklet.rs b/rust/stackablectl/src/cmds/stacklet.rs index dd94a864..55c3b6e8 100644 --- a/rust/stackablectl/src/cmds/stacklet.rs +++ b/rust/stackablectl/src/cmds/stacklet.rs @@ -9,7 +9,7 @@ use tracing::{info, instrument}; use stackable_cockpit::{ constants::DEFAULT_PRODUCT_NAMESPACE, platform::stacklet::{self, get_credentials_for_product, list_stacklets}, - utils::k8s::DisplayCondition, + utils::k8s::{self, Client, DisplayCondition}, }; use crate::{ @@ -76,6 +76,9 @@ pub enum CmdError { #[snafu(display("failed to serialize JSON output"))] SerializeJsonOutput { source: serde_json::Error }, + + #[snafu(display("failed to create Kubernetes client"))] + KubeClientCreate { source: k8s::Error }, } impl StackletArgs { @@ -91,10 +94,12 @@ impl StackletArgs { async fn list_cmd(args: &StackletListArgs, cli: &Cli) -> Result { info!("Listing installed stacklets"); + let client = Client::new().await.context(KubeClientCreateSnafu)?; + // If the user wants to list stacklets from all namespaces, we use `None`. // `None` indicates that don't want to list stacklets scoped to only ONE // namespace. - let stacklets = list_stacklets(Some(&args.namespaces.product_namespace)) + let stacklets = list_stacklets(&client, Some(&args.namespaces.product_namespace)) .await .context(StackletListSnafu)?; @@ -202,7 +207,10 @@ async fn list_cmd(args: &StackletListArgs, cli: &Cli) -> Result Result { info!("Displaying stacklet credentials"); + let client = Client::new().await.context(KubeClientCreateSnafu)?; + match get_credentials_for_product( + &client, &args.product_namespace, &args.stacklet_name, &args.product_name,