use chrono::{DateTime, Local, TimeZone};
use chrono::offset::Utc;
use duct::cmd;
use serde_json::{self, Value};
use serde_yaml;
use std::cell::RefCell;
use std::error::Error;
use std::fs::File;
use std::io;
#[derive(Debug, Deserialize)]
pub struct Config {
pub clusters: Vec<Cluster>,
pub contexts: Vec<Context>,
pub users: Vec<User>,
}
impl Config {
pub fn from_file(path: &str) -> Result<Config, io::Error> {
let f = File::open(path)?;
serde_yaml::from_reader(f).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Couldn't read yaml in '{}': {}", path, e.description()),
)
})
}
}
#[derive(Debug, Deserialize)]
pub struct Cluster {
pub name: String,
#[serde(rename = "cluster")] pub conf: ClusterConf,
}
#[derive(Debug, Deserialize)]
pub struct ClusterConf {
#[serde(rename = "certificate-authority")] pub cert: Option<String>,
#[serde(rename = "certificate-authority-data")] pub cert_data: Option<String>,
#[serde(rename = "insecure-skip-tls-verify")] pub skip_tls: Option<bool>,
pub server: String,
}
#[derive(Debug, Deserialize)]
pub struct Context {
pub name: String,
#[serde(rename = "context")] pub conf: ContextConf,
}
#[derive(Debug, Deserialize)]
pub struct User {
pub name: String,
#[serde(rename = "user")] pub conf: UserConf,
}
#[derive(Debug, Deserialize, Clone)]
pub struct UserConf {
pub token: Option<String>,
#[serde(rename = "client-certificate")] pub client_cert: Option<String>,
#[serde(rename = "client-key")] pub client_key: Option<String>,
#[serde(rename = "client-certificate-data")] pub client_cert_data: Option<String>,
#[serde(rename = "client-key-data")] pub client_key_data: Option<String>,
pub username: Option<String>,
pub password: Option<String>,
#[serde(rename = "auth-provider")] pub auth_provider: Option<AuthProvider>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ContextConf {
pub cluster: String,
pub namespace: Option<String>,
pub user: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct AuthProvider {
name: String,
pub token: RefCell<Option<String>>,
pub expiry: RefCell<Option<String>>,
pub config: AuthProviderConfig,
}
impl AuthProvider {
pub fn copy_up(&self) {
let mut token = self.token.borrow_mut();
*token = self.config.access_token.clone();
let mut expiry = self.expiry.borrow_mut();
*expiry = self.config.expiry.clone();
}
fn check_dt<T: TimeZone>(&self, expiry: DateTime<T>) -> bool {
let etime = expiry.with_timezone(&Utc);
let now = Utc::now();
etime < now
}
fn is_expired(&self) -> bool {
let expiry = self.expiry.borrow();
match *expiry {
Some(ref e) => {
if let Ok(expiry) = DateTime::parse_from_rfc3339(e) {
self.check_dt(expiry)
} else if let Ok(expiry) = Local.datetime_from_str(e, "%Y-%m-%d %H:%M:%S") {
self.check_dt(expiry)
} else {
true
}
}
None => {
println!("No expiry set, cannot validate if token is still valid");
false
}
}
}
fn make_pointer(&self, s: &str) -> String {
let l = s.len() - 1;
let split = &s[1..l].split('.');
split.clone().collect::<Vec<&str>>().join("/")
}
fn update_token(&self, token: &mut Option<String>, expiry: &mut Option<String>) {
match self.config.cmd_path {
Some(ref conf_cmd) => {
let args = self.config
.cmd_args
.as_ref()
.map(|argstr| argstr.split_whitespace().collect())
.unwrap_or(vec![]);
match cmd(conf_cmd, &args).read() {
Ok(output) => {
let v: Value = serde_json::from_str(output.as_str()).unwrap();
let mut updated_token = false;
match self.config.token_key.as_ref() {
Some(ref tk) => {
let token_pntr = self.make_pointer(tk.as_str());
let extracted_token =
v.pointer(token_pntr.as_str()).and_then(|tv| tv.as_str());
*token = extracted_token.map(|t| t.to_owned());
updated_token = true;
}
None => {
println!("No token-key in auth-provider, cannot extract token");
}
}
if updated_token {
match self.config.expiry_key.as_ref() {
Some(ref ek) => {
let expiry_pntr = self.make_pointer(ek.as_str());
let extracted_expiry =
v.pointer(expiry_pntr.as_str()).and_then(|ev| ev.as_str());
*expiry = extracted_expiry.map(|e| e.to_owned());
}
None => {
println!(
"No expiry-key in config, will have to pull a new \
token on every command"
);
}
}
}
}
Err(e) => {
println!("Failed to run update command: {}", e);
}
}
}
None => {
println!("No update command specified, can't update");
}
}
}
pub fn ensure_token(&self) -> Option<String> {
let mut token = self.token.borrow_mut();
if token.is_none() || self.is_expired() {
let mut expiry = self.expiry.borrow_mut();
*token = None;
self.update_token(&mut token, &mut expiry)
}
token.clone()
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct AuthProviderConfig {
#[serde(rename = "access-token")] pub access_token: Option<String>,
expiry: Option<String>,
#[serde(rename = "cmd-args")] cmd_args: Option<String>,
#[serde(rename = "cmd-path")] cmd_path: Option<String>,
#[serde(rename = "expiry-key")] expiry_key: Option<String>,
#[serde(rename = "token-key")] token_key: Option<String>,
}