Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/config/kube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use kube::{ClientCertKey, Kluster, KlusterAuth};
use certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key};

use super::kubefile::AuthProvider;
use super::kubefile::Exec;

#[derive(Debug)]
pub struct ClusterConf {
Expand Down Expand Up @@ -64,6 +65,7 @@ pub enum UserAuth {
KeyCertPath(String, String),
KeyCertData(String, String),
UserPass(String, String),
Exec(Exec),
AuthProvider(AuthProvider),
}

Expand All @@ -82,6 +84,9 @@ impl From<super::kubefile::UserConf> for UserConf {
if let (Some(username), Some(password)) = (conf.username, conf.password) {
auth_vec.push(UserAuth::UserPass(username, password))
}
if let Some(exec) = conf.exec {
auth_vec.push(UserAuth::Exec(exec))
}
if let (Some(client_cert_path), Some(key_path)) =
(conf.client_cert, conf.client_key)
{
Expand Down Expand Up @@ -336,6 +341,9 @@ impl Config {
KlusterAuth::with_userpass(username, password)
)
}
&UserAuth::Exec(ref exec) => {
auth = Some(KlusterAuth::with_exec(exec.clone()))
}
&UserAuth::AuthProvider(ref provider) => {
provider.copy_up();
auth =
Expand Down
88 changes: 88 additions & 0 deletions src/config/kubefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ use serde_json::{self, Value};
use serde_yaml;

use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io;
use std::process::Command;

//use error::{KubeErrNo, KubeError};
//use kube::{Kluster, KlusterAuth};
Expand Down Expand Up @@ -92,6 +94,7 @@ pub struct UserConf {

pub username: Option<String>,
pub password: Option<String>,
pub exec: Option<Exec>,

#[serde(rename = "auth-provider")] pub auth_provider: Option<AuthProvider>,
}
Expand All @@ -104,6 +107,91 @@ pub struct ContextConf {
pub user: String,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Exec {
#[serde(rename = "apiVersion")]
api_version: String,
pub args: Option<Vec<String>>,
pub command: Option<String>,
pub env: Option<Vec<Env>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not just make this an Option<HashMap<String, String>> directly? I think serde should just "do the right thing" in that case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this and the suggestions below. It compiles fine, but when I start click it gives the following error which I don't understand:

$ cargo build && ./target/debug/click
   Compiling click v0.4.2 (/Users/user/Code/click/click)
    Finished dev [unoptimized + debuginfo] target(s) in 8.77s
Could not load kubernetes config. Cannot continue.  Error was: Couldn't read yaml in '/Users/user/.kube/config': invalid type: sequence, expected a map

I'm not understanding why, as the generate_token function is not even called when starting, just after the first request to the API.

Any ways I can debug this faster?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicklan
Apparently serde doesn't like parsing the following when it's a

Option<HashMap<String, String>>

users:
- name: eks-acceptance
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - --region
      - eu-west-1
      - eks
      - get-token
      - --cluster-name
      - eks-acceptance
      command: aws
      env:
      - name: AWS_PROFILE
        value: default

I suppose it's something you could test too, it happens just after starting the application. For some reason it doesn't like the env. Not sure why.

pub token: RefCell<Option<String>>,
pub expiry: RefCell<Option<DateTime<Utc>>>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Env {
name: String,
value: String,
}

#[derive(Serialize, Deserialize)]
struct Spec {}

#[derive(Serialize, Deserialize)]
struct Status {
token: String,
}

#[derive(Serialize, Deserialize)]
struct AWSCredential {
kind: String,
#[serde(rename = "apiVersion")]
api_version: String,
spec: Spec,
status: Status,
}

impl Exec {
// true if expiry is 10 minutes ago or more
fn check_dt<T: TimeZone>(&self, expiry: DateTime<T>) -> bool {
let etime = expiry.with_timezone(&Utc);
let now = Utc::now();
let diff = now.signed_duration_since(etime);
return diff.num_minutes() >= 10;
}

fn is_expired(&self) -> bool {
let expiry = self.expiry.borrow();
match *expiry {
Some(e) => {
self.check_dt(e)
}
None => {
true
}
}
}

fn generate_token(&self) -> String {
let mut filtered_env: HashMap<String, String> = HashMap::new();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make the above change to have self.env be a HashMap, this shouldn't be needed. Below you can just do:

let output = Command::new(self.command.clone().unwrap())
            .args(self.args.clone().unwrap())
            .envs(&self.env)
            .output()
            .expect("failed to execute process");

Without testing myself I'm not sure you won't have to clone it, since envs want's an IntoIter, so you may need to do that.

for e in self.env.clone().unwrap() {
filtered_env.insert(e.name, e.value);
}

let output = Command::new(self.command.clone().unwrap())
.args(self.args.clone().unwrap())
.envs(filtered_env)
.output()
.expect("failed to execute process");

let out: String = String::from_utf8_lossy(&output.stdout).to_string();
let v: AWSCredential = serde_json::from_str(&out).unwrap();
let token = v.status.token;
return token;
}

/// Checks that we have a valid token, and if not, attempts to update it based on the config
pub fn ensure_token(&self) -> Option<String> {
let mut token = self.token.borrow_mut();
if self.is_expired() {
let mut expiry = self.expiry.borrow_mut();
*token = Some(self.generate_token());
let v = Utc::now();
*expiry = Some(v)
}
token.clone()
}
}

// Classes to hold deserialized data for auth
#[derive(Debug, Deserialize, Clone)]
Expand Down
3 changes: 2 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pub use self::click::EditMode;
pub use self::kube::Config;

pub use self::kubefile::AuthProvider;
pub use self::kubefile::ContextConf;
pub use self::kubefile::Exec;
pub use self::kubefile::ContextConf;
22 changes: 22 additions & 0 deletions src/kube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use std::sync::Arc;
use std::time::Duration;

use config::AuthProvider;
use config::Exec;
use connector::ClickSslConnector;
use error::{KubeErrNo, KubeError};

Expand Down Expand Up @@ -345,6 +346,7 @@ pub struct JobList {
pub enum KlusterAuth {
Token(String),
UserPass(String, String),
Exec(Exec),
AuthProvider(AuthProvider),
}

Expand All @@ -357,6 +359,10 @@ impl KlusterAuth {
KlusterAuth::UserPass(user.to_owned(), pass.to_owned())
}

pub fn with_exec(exec: Exec) -> KlusterAuth {
KlusterAuth::Exec(exec.to_owned())
}

pub fn with_auth_provider(auth_provider: AuthProvider) -> KlusterAuth {
KlusterAuth::AuthProvider(auth_provider)
}
Expand Down Expand Up @@ -487,6 +493,15 @@ impl Kluster {
}
}
}
Some(KlusterAuth::Exec(ref exec)) => {
match exec.ensure_token() {
Some(token) => req.header(Authorization(Bearer { token: token })),
None => {
print_token_err();
req
}
}
}
Some(KlusterAuth::UserPass(ref user, ref pass)) => req.header(Authorization(Basic {
username: user.clone(),
password: Some(pass.clone()),
Expand Down Expand Up @@ -578,6 +593,13 @@ impl Kluster {
None => print_token_err(),
}
}
Some(KlusterAuth::Exec(ref _exec)) => {
match _exec.ensure_token() {
Some(token) => headers.set(
Authorization(Bearer { token: token })),
None => print_token_err(),
}
}
Some(KlusterAuth::UserPass(ref user, ref pass)) => {
headers.set(Authorization(Basic {
username: user.clone(),
Expand Down