1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use std::path::Path;
use std::fs::File;
use std::fs::read_to_string;
use std::io::Result as IoResult;

use serde::Deserialize;
use dirs::home_dir;

use crate::ConfigError;

#[derive(Debug, PartialEq, Deserialize)]
pub struct Cluster {
    pub name: String,
    pub cluster: ClusterDetail,
}


#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ClusterDetail {
    pub insecure_skip_tls_verify: Option<bool>,
    pub certificate_authority: Option<String>,
    pub certificate_authority_data: Option<String>,
    pub server: String,
}

impl ClusterDetail {
    pub fn ca(&self) -> Option<IoResult<String>> {

        self.certificate_authority.as_ref().map(|ca| read_to_string(ca))
    }
}

#[derive(Debug, PartialEq, Deserialize)]
pub struct Context {
    pub name: String,
    pub context: ContextDetail,
}


#[derive(Debug, PartialEq, Deserialize)]
pub struct ContextDetail {
    pub cluster: String,
    pub user: String,
    pub namespace: Option<String>
}

impl ContextDetail {
    pub fn namespace(&self) -> &str {
        match &self.namespace {
            Some(nm) => &nm,
            None => "default"
        }
    }
}


#[derive(Debug, PartialEq, Deserialize)]
pub struct User {
    pub name: String,
    pub user: UserDetail
}

#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct UserDetail {
    pub client_certificate: Option<String>,
    pub client_key: Option<String>
}


#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct KubeConfig {
    #[serde(rename = "apiVersion")]
    pub api_version: String,
    pub clusters: Vec<Cluster>,
    pub contexts: Vec<Context>,
    pub current_context: String,
    pub kind: String,
    pub users: Vec<User>,
}


impl KubeConfig {

    /// read from default home directory
    pub fn from_home() -> Result<Self,ConfigError> {
        let home_dir = home_dir().unwrap();
        Self::from_file(home_dir.join(".kube").join("config"))
    }

    pub fn from_file<T: AsRef<Path>>(path: T) -> Result<Self,ConfigError> {
        let file = File::open(path)?;
        Ok(serde_yaml::from_reader(file)?)
    }

    pub fn current_context(&self) -> Option<&Context> {
        self.contexts.iter().find(|c| c.name == self.current_context)
    }

    pub fn current_cluster(&self) -> Option<&Cluster> {
        if let Some(ctx) = self.current_context() {
             self.clusters.iter().find(|c| c.name == ctx.context.cluster)
        } else {
            None
        }  
    }

    pub fn current_user(&self) -> Option<&User> {
        if let Some(ctx) = self.current_context() {
            self.users.iter().find(|c| c.name == ctx.context.user)
        } else {
            None
        }
    }


}




#[cfg(test)]
mod test {

    use super::KubeConfig;

    #[test]
    fn test_decode_default_config() {
        let config = KubeConfig::from_file("data/k8config.yaml").expect("read");
        assert_eq!(config.api_version,"v1");
        assert_eq!(config.kind,"Config");
        assert_eq!(config.current_context,"flv");
        assert_eq!(config.clusters.len(),1);
        let cluster = &config.clusters[0].cluster;
        assert_eq!(cluster.server,"https://192.168.0.0:8443");
        assert_eq!(cluster.certificate_authority,Some("/Users/test/.minikube/ca.crt".to_owned()));
        assert_eq!(config.contexts.len(),2);
        let ctx = &config.contexts[0].context;
        assert_eq!(ctx.cluster,"minikube");
        assert_eq!(ctx.namespace.as_ref().unwrap(),"flv");

        let current_cluster = config.current_cluster().expect("current");
        assert_eq!(current_cluster.name,"minikube");

    }
}