Skip to content

Commit

Permalink
Merge pull request #450 from sarub0b0/425-feature-decoding-helm-secret
Browse files Browse the repository at this point in the history
feat(k8s/secret): Support decoding `helm.sh/release.v1` secret type
  • Loading branch information
sarub0b0 authored Dec 29, 2023
2 parents 4b1bb7e + e34d325 commit 7f7a1a4
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 92 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -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"}
118 changes: 27 additions & 91 deletions src/event/kubernetes/config/data/secret.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
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> {
client: &'a KubeClient,
namespace: String,
name: String,
}

#[async_trait()]
impl<'a> Fetch<'a> for SecretDataWorker<'a> {
fn new(client: &'a KubeClient, namespace: String, name: String) -> Self {
Expand All @@ -31,103 +31,39 @@ impl<'a> Fetch<'a> for SecretDataWorker<'a> {
name,
}
}

async fn fetch(&self) -> Result<ConfigData> {
let list: Api<Secret> = 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<String, ByteString>);
let type_ = target.type_.as_deref().unwrap_or_default();

impl SecretData {
fn to_string_key_values(&self) -> Vec<String> {
let ret: Vec<String> = self
.iter()
.flat_map(|key_value| {
key_value
.lines()
.map(ToString::to_string)
.collect::<Vec<String>>()
})
.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<String, ByteString>) -> Result<Self> {
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<<Self as Iterator>::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(),
}
}
}
63 changes: 63 additions & 0 deletions src/event/kubernetes/config/data/secret/any.rs
Original file line number Diff line number Diff line change
@@ -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<String, ByteString>,
}

impl Any {
pub fn new(data: BTreeMap<String, ByteString>) -> Self {
Self { data }
}

pub fn to_string_key_values(&self) -> Vec<String> {
self.iter()
.flat_map(|key_value| {
key_value
.lines()
.map(ToString::to_string)
.collect::<Vec<String>>()
})
.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<<Self as Iterator>::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))
}
}
}
}
31 changes: 31 additions & 0 deletions src/event/kubernetes/config/data/secret/format.rs
Original file line number Diff line number Diff line change
@@ -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
)
}
71 changes: 71 additions & 0 deletions src/event/kubernetes/config/data/secret/helm.rs
Original file line number Diff line number Diff line change
@@ -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<String, ByteString>,
}

impl Helm {
pub fn new(data: BTreeMap<String, ByteString>) -> Self {
Self { data }
}

pub fn to_string_key_values(&self) -> Vec<String> {
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<String> {
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::<serde_yaml::Value>(&decoded)?;

serde_yaml::to_string(&yaml).map_err(Into::into)
}

0 comments on commit 7f7a1a4

Please sign in to comment.