rkubectl-app 0.0.0-alpha.5

The application logic for rkubectl, a Kubernetes CLI tool
Documentation
use ext::Base64Decode;
use flate2::read::GzDecoder;
use k8s::SecretExt;
use k8s::openapi::ByteString;

use super::*;

#[derive(Clone, Debug, Subcommand)]
pub enum Secret {
    Show {
        secret: String,
        data: Option<Vec<String>>,
    },
}

impl Secret {
    const SECRET_TYPE_HELM_SH_RELEASE_V1: &'static str = "helm.sh/release.v1";

    pub async fn exec(&self, context: &Context) -> RkResult<()> {
        match self {
            Self::Show { secret, data } => self.show(context, secret, data.as_deref()).await,
        }
    }

    async fn show(&self, context: &Context, secret: &str, data: Option<&[String]>) -> RkResult<()> {
        let secret = context.kubeapi().secrets()?.get(secret).await?;
        let r#type = secret.type_.as_deref().unwrap_or("Opaque");
        let items = secret
            .data
            .unwrap_or_default()
            .into_iter()
            .filter(|(key, _)| data.is_none_or(|items| items.contains(key)))
            .collect::<Vec<_>>();
        decode_items(r#type, items);

        Ok(())
    }
}

fn decode_items(r#type: &str, items: Vec<(String, ByteString)>) {
    let single_item = items.len() == 1;
    items
        .into_iter()
        .map(|(key, value)| (key, decode(r#type, value)))
        .for_each(|(key, item)| {
            if single_item {
                println!("{item}")
            } else {
                println!("{key}: {item}")
            }
        });
}

fn decode(r#type: &str, value: ByteString) -> String {
    match r#type {
        Secret::SECRET_TYPE_HELM_SH_RELEASE_V1 => decode_helm_sh_release_v1(value),
        corev1::Secret::SECRET_TYPE_DOCKER_CONFIG_JSON => decode_docker_config_json(value),
        corev1::Secret::SECRET_TYPE_SERVICE_ACCOUNT_TOKEN => decode_service_account_token(value),
        corev1::Secret::SECRET_TYPE_OPAQUE => decode_generic(value),
        _other => decode_generic(value),
    }
}

fn decode_generic(item: ByteString) -> String {
    String::from_utf8_lossy(&item.0).to_string()
}

fn decode_docker_config_json(item: ByteString) -> String {
    if let Ok(value) = json::from_slice::<json::Value>(&item.0) {
        json::to_string_pretty(&value).unwrap_or_default()
    } else {
        String::from_utf8_lossy(&item.0).to_string()
    }
}

fn decode_service_account_token(item: ByteString) -> String {
    String::from_utf8_lossy(&item.0).to_string()
}

fn decode_helm_sh_release_v1(item: ByteString) -> String {
    use io::Read;
    let bytes: Vec<u8> = item.decode().unwrap_or_default();
    let mut decoder = GzDecoder::new(bytes.as_slice());
    let mut text = String::with_capacity(bytes.len() * 3);
    decoder.read_to_string(&mut text).unwrap_or(0);
    text
}