From 93ef49a44a527bc57e02c731209b5a00f1f8aa59 Mon Sep 17 00:00:00 2001 From: kosay Date: Wed, 20 Dec 2023 22:49:50 +0900 Subject: [PATCH 1/2] feat(k8s/secret): Support decoding helm.sh/release.v1 secret type. --- Cargo.lock | 1 + Cargo.toml | 1 + src/event/kubernetes/config/data/secret.rs | 118 ++++-------------- .../kubernetes/config/data/secret/any.rs | 63 ++++++++++ .../kubernetes/config/data/secret/format.rs | 31 +++++ .../kubernetes/config/data/secret/helm.rs | 71 +++++++++++ 6 files changed, 194 insertions(+), 91 deletions(-) create mode 100644 src/event/kubernetes/config/data/secret/any.rs create mode 100644 src/event/kubernetes/config/data/secret/format.rs create mode 100644 src/event/kubernetes/config/data/secret/helm.rs diff --git a/Cargo.lock b/Cargo.lock index 6a64e2d2..fc7e1b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,6 +1058,7 @@ dependencies = [ "ctrlc", "derivative", "enum_dispatch", + "flate2", "futures", "fuzzy-matcher", "http", diff --git a/Cargo.toml b/Cargo.toml index ad3c1434..663ec7b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ regex = "1.10.2" regex-syntax = { version = "0.8.2", default-features = false } indoc = "2.0.4" +flate2 = "1.0.28" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/src/event/kubernetes/config/data/secret.rs b/src/event/kubernetes/config/data/secret.rs index 6513ad36..5fb25dca 100644 --- a/src/event/kubernetes/config/data/secret.rs +++ b/src/event/kubernetes/config/data/secret.rs @@ -1,19 +1,20 @@ -use std::collections::{btree_map, BTreeMap}; +mod any; +mod format; +mod helm; + +use std::collections::BTreeMap; use async_trait::async_trait; -use base64::{engine::general_purpose, Engine}; use k8s_openapi::{api::core::v1::Secret, ByteString}; use kube::Api; use crate::{ error::Result, - event::kubernetes::{ - client::KubeClient, - color::{fg, Color}, - config::ConfigData, - }, + event::kubernetes::{client::KubeClient, config::ConfigData}, }; +use self::{any::Any, helm::Helm}; + use super::Fetch; pub(super) struct SecretDataWorker<'a> { @@ -21,7 +22,6 @@ pub(super) struct SecretDataWorker<'a> { namespace: String, name: String, } - #[async_trait()] impl<'a> Fetch<'a> for SecretDataWorker<'a> { fn new(client: &'a KubeClient, namespace: String, name: String) -> Self { @@ -31,103 +31,39 @@ impl<'a> Fetch<'a> for SecretDataWorker<'a> { name, } } - async fn fetch(&self) -> Result { let list: Api = Api::namespaced(self.client.as_client().clone(), &self.namespace); let target = list.get(&self.name).await?; - if let Some(data) = target.data { - let data = SecretData(data); - Ok(data.to_string_key_values()) - } else { - Ok(vec!["no data".into()]) - } - } -} - -#[derive(Debug, Default)] -struct SecretData(BTreeMap); + let type_ = target.type_.as_deref().unwrap_or_default(); -impl SecretData { - fn to_string_key_values(&self) -> Vec { - let ret: Vec = self - .iter() - .flat_map(|key_value| { - key_value - .lines() - .map(ToString::to_string) - .collect::>() - }) - .collect(); - - ret - } + let Some(data) = target.data else { + return Ok(vec!["no data".into()]); + }; - fn iter(&self) -> Iter { - Iter { - iter: self.0.iter(), - color: Color::new(), - } + let data = SecretData::new(type_, data)?; + Ok(data.to_string_key_values()) } } -struct Iter<'a> { - iter: btree_map::Iter<'a, String, ByteString>, - color: Color, +#[derive(Debug)] +enum SecretData { + Helm(Helm), + Any(Any), } -impl Iter<'_> { - fn format_utf8(key: &str, value: &str, color: u8) -> String { - if value.contains('\n') { - let mut ret = format!("\x1b[{color}m{key}:\x1b[39m |\n", color = color, key = key); - - value.lines().for_each(|l| { - ret += &format!(" {}\n", l); - }); - - ret - } else { - format!( - "\x1b[{color}m{key}:\x1b[39m {value}", - color = color, - key = key, - value = value, - ) +impl SecretData { + fn new(type_: &str, data: BTreeMap) -> Result { + match type_ { + "helm.sh/release.v1" => Ok(Self::Helm(Helm::new(data))), + _ => Ok(Self::Any(Any::new(data))), } } - fn format_error(key: &str, value: &str, err: &str, color: u8) -> String { - format!( - "\x1b[{color}m{key}:\x1b[39m | \x1b[{error_color}m# {error}\x1b[39m\n [base64-encoded] {value}", - color = color, - key = key, - value = value, - error_color = fg::Color::DarkGray as u8, - error = err - ) - } -} - -impl Iterator for Iter<'_> { - type Item = String; - fn next(&mut self) -> std::option::Option<::Item> { - let Some((key, ByteString(value))) = self.iter.next() else { - return None; - }; - - let color = self.color.next_color(); - - match String::from_utf8(value.to_vec()) { - Ok(utf8_data) => Some(Self::format_utf8(key, &utf8_data, color)), - Err(err) => { - let base64_encoded = general_purpose::STANDARD.encode(value); - Some(Self::format_error( - key, - &base64_encoded, - &err.to_string(), - color, - )) - } + fn to_string_key_values(&self) -> ConfigData { + match self { + Self::Helm(helm) => helm.to_string_key_values(), + Self::Any(any) => any.to_string_key_values(), } } } diff --git a/src/event/kubernetes/config/data/secret/any.rs b/src/event/kubernetes/config/data/secret/any.rs new file mode 100644 index 00000000..cc5759e2 --- /dev/null +++ b/src/event/kubernetes/config/data/secret/any.rs @@ -0,0 +1,63 @@ +use std::collections::{btree_map, BTreeMap}; + +use base64::{engine::general_purpose, Engine}; +use k8s_openapi::ByteString; + +use crate::event::kubernetes::color::Color; + +use super::format::{format_error, format_utf8}; + +/// any type secret +#[derive(Debug, Default)] +pub struct Any { + data: BTreeMap, +} + +impl Any { + pub fn new(data: BTreeMap) -> Self { + Self { data } + } + + pub fn to_string_key_values(&self) -> Vec { + self.iter() + .flat_map(|key_value| { + key_value + .lines() + .map(ToString::to_string) + .collect::>() + }) + .collect() + } + + fn iter(&self) -> Iter { + Iter { + iter: self.data.iter(), + color: Color::new(), + } + } +} + +struct Iter<'a> { + iter: btree_map::Iter<'a, String, ByteString>, + color: Color, +} + +impl Iterator for Iter<'_> { + type Item = String; + fn next(&mut self) -> std::option::Option<::Item> { + let Some((key, ByteString(value))) = self.iter.next() else { + return None; + }; + + let color = self.color.next_color(); + + match String::from_utf8(value.to_vec()) { + Ok(utf8_data) => Some(format_utf8(key, &utf8_data, color)), + Err(err) => { + let base64_encoded = general_purpose::STANDARD.encode(value); + + Some(format_error(key, &base64_encoded, &err.to_string(), color)) + } + } + } +} diff --git a/src/event/kubernetes/config/data/secret/format.rs b/src/event/kubernetes/config/data/secret/format.rs new file mode 100644 index 00000000..6bb2eccf --- /dev/null +++ b/src/event/kubernetes/config/data/secret/format.rs @@ -0,0 +1,31 @@ +use crate::event::kubernetes::color::fg::Color; + +pub(super) fn format_utf8(key: &str, value: &str, color: u8) -> String { + if value.contains('\n') { + let mut ret = format!("\x1b[{color}m{key}:\x1b[39m |\n", color = color, key = key); + + value.lines().for_each(|l| { + ret += &format!(" {}\n", l); + }); + + ret.trim_end().to_string() + } else { + format!( + "\x1b[{color}m{key}:\x1b[39m {value}", + color = color, + key = key, + value = value, + ) + } +} + +pub(super) fn format_error(key: &str, value: &str, err: &str, color: u8) -> String { + format!( + "\x1b[{color}m{key}:\x1b[39m | \x1b[{error_color}m# {error}\x1b[39m\n [base64-encoded] {value}", + color = color, + key = key, + value = value, + error_color = Color::DarkGray as u8, + error = err + ) +} diff --git a/src/event/kubernetes/config/data/secret/helm.rs b/src/event/kubernetes/config/data/secret/helm.rs new file mode 100644 index 00000000..b721b40b --- /dev/null +++ b/src/event/kubernetes/config/data/secret/helm.rs @@ -0,0 +1,71 @@ +use std::{collections::BTreeMap, io::prelude::*}; + +use anyhow::Result; +use base64::{engine::general_purpose, Engine}; +use k8s_openapi::ByteString; + +use crate::event::kubernetes::color::{self, Color}; + +use super::format::{format_error, format_utf8}; + +#[derive(Debug, Default)] +pub struct Helm { + data: BTreeMap, +} + +impl Helm { + pub fn new(data: BTreeMap) -> Self { + Self { data } + } + + pub fn to_string_key_values(&self) -> Vec { + let Some(ByteString(value)) = self.data.get("release") else { + return vec!["no release data".into()]; + }; + + let mut color = Color::new(); + + let decoded_release = match decode_release(value) { + Ok(decoded) => { + let color = color.next_color(); + format_utf8("release (decoded)", &decoded, color) + } + Err(err) => { + format!( + "\x1b[{red}m# Failed to decode the 'release' value: {err}\x1b[39m", + red = color::fg::Color::Red as u8, + err = err + ) + } + }; + + let color = color.next_color(); + + let release = match String::from_utf8(value.to_vec()) { + Ok(utf8_data) => format_utf8("release", &utf8_data, color), + Err(err) => { + let base64_encoded = general_purpose::STANDARD.encode(value); + format_error("release", &base64_encoded, &err.to_string(), color) + } + }; + + decoded_release + .lines() + .chain(release.lines()) + .map(ToString::to_string) + .collect() + } +} + +fn decode_release(data: &[u8]) -> Result { + let gzip = general_purpose::STANDARD.decode(data)?; + + // decode gzip + let mut decoder = flate2::read::GzDecoder::new(&gzip[..]); + let mut decoded = String::new(); + decoder.read_to_string(&mut decoded)?; + + let yaml = serde_yaml::from_str::(&decoded)?; + + serde_yaml::to_string(&yaml).map_err(Into::into) +} From e34d325bc925c259ba08568a02613c421e1d6919 Mon Sep 17 00:00:00 2001 From: kohashimoto Date: Fri, 29 Dec 2023 23:56:00 +0900 Subject: [PATCH 2/2] chore: update cspell.json --- cspell.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cspell.json b/cspell.json index 52672157..95ca9d35 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"version":"0.2","flagWords":[],"words":["ratatui","crossterm","kubetui","Atext","multispace","nums","arboard","kohashimoto","kubeconfig","hoge","rstest","thiserror","openapi","Kubeconfig","indoc","apimachinery","horizontalpodautoscalers","mockall","configmap","chrono","networkpolicies","networkpolicy","serviceaccount","jdfbz","tolerations","pullable","fuga","coldef","Appender","logfile","appender","Fullscreen","activatable","rustfmt","tesat","tesatb","fullwidth","mabb","peekable","taskbox","ptimized","mhoge","oneline","maaaaaaaaaaaaaaa","impls","showable","mabc","HOGE","hogehoge","subwin","erde","rustls","taplo","kosay","serde","itertools","anychar","statefulset","statefulsets","replicaset","replicasets","daemonset","resoruces"],"language":"en"} +{"version":"0.2","flagWords":[],"words":["ratatui","crossterm","kubetui","Atext","multispace","nums","arboard","kohashimoto","kubeconfig","hoge","rstest","thiserror","openapi","Kubeconfig","indoc","apimachinery","horizontalpodautoscalers","mockall","configmap","chrono","networkpolicies","networkpolicy","serviceaccount","jdfbz","tolerations","pullable","fuga","coldef","Appender","logfile","appender","Fullscreen","activatable","rustfmt","tesat","tesatb","fullwidth","mabb","peekable","taskbox","ptimized","mhoge","oneline","maaaaaaaaaaaaaaa","impls","showable","mabc","HOGE","hogehoge","subwin","erde","rustls","taplo","kosay","serde","itertools","anychar","statefulset","statefulsets","replicaset","replicasets","daemonset","resoruces","flate"],"language":"en"}