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
150
151
152
153
//! Welcome to the `kube-conf` crate.
//!
//! This crate is a convenient way of fetching the local user's kubernetes
//! config file and reading the values.
//!
//! # Examples
//!
//! ## Fetching current context
//!
//! ```
//! use kube_conf::Config;
//! let config = Config::load("tests/config.yml")?;
//! let current_context = config.get_current_context().unwrap();
//!
//! assert_eq!("dev-frontend", current_context.name);
//! # Ok::<(), kube_conf::errors::Error>(())
//! ```
//!
//! ## Fetching the default kubeconfig file
//!
//! This typically means the file located at `$HOME/.kube/config`
//!
//! ```
//! use kube_conf::Config;
//! # use std::env::{set_var, current_dir};
//! # set_var("HOME", format!("{}/tests", current_dir().unwrap().to_str().unwrap()));
//! let config = Config::load_default()?;
//! let current_context = config.current_context.unwrap();
//!
//! assert_eq!("dev-frontend", current_context);
//! # Ok::<(), kube_conf::errors::Error>(())
//! ```

// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]

#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;
extern crate serde_yaml;

pub mod cluster;
pub mod context;
mod get;
pub mod user;

/// A module that exposes errors thrown by the crate.
///
/// These errors are a chain generated from the the
/// [error-chain](https://crates.io/crates/error-chain) crate. This is so that
/// the underlying errors are not lost. See the
/// [ErrorKind](errors/enum.ErrorKind.html) enum for the various errors that can
/// be returned.
#[allow(deprecated)]
pub mod errors {
    // Create the Error, ErrorKind, ResultExt, and Result types
    error_chain! {
        foreign_links {
            Yaml(serde_yaml::Error) #[doc = "Error when parsing a yaml file"];
        }
        errors {
            /// If a config file does not exist (at the given path) this error
            /// will be returned
            MissingConfigFile(f: String) {
                description("config file was not found at the specified path"),
                display("missing config file: '{}'", f),
            }
        }
    }
}

use cluster::Cluster;
use context::Context;
use errors::*;
use serde_yaml::Mapping;
use std::env;
use std::fs::read_to_string;
use std::path::Path;
use user::User;

/// The main struct that holds the entire config map.
/// See the methods on this struct for ways to parse a config.
#[serde(rename_all = "kebab-case")]
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
    /// The name of the current active context.
    /// The actual context can be retrieved by finding the context in the
    /// context set based on this name.
    pub current_context: Option<String>,

    /// Preferences provided in the config.yml file.
    pub preferences: Option<Mapping>,

    /// The clusters as defined by the "clusters" key
    pub clusters: Vec<Cluster>,

    /// The contexts as defined by the "contexts" key
    pub contexts: Vec<Context>,

    /// The users as defined by the "users" key
    pub users: Vec<User>,

    /// Will typically be "v1", generally not needed
    #[serde(rename = "apiVersion")]
    pub api_version: Option<String>,

    /// Will typically be "Config", generally not needed
    pub kind: Option<String>,
}

impl Config {
    /// Fetches the current config based on the user's configured environment.
    ///
    /// This includes `$KUBECONFIG` when set, or simply `$HOME/.kube/config`
    /// otherwise.
    ///
    /// TODO: Support multiple kubeconfig files in the KUBECONFIG env var
    /// separated by colons, i.e. `KUBECONFIG=file1:file2`. Merge the result.
    pub fn load_default() -> Result<Config> {
        if let Ok(conf_path) = env::var("KUBECONFIG") {
            return Config::load(&conf_path);
        }

        if let Ok(home) = env::var("HOME") {
            return Config::load(&format!("{}/.kube/config", &home));
        }

        bail!("Neither $KUBECONFIG nor $HOME are defined");
    }

    /// Fetches the config from the provided path.
    pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> {
        let conf = read_to_string(&path)
            .chain_err(|| ErrorKind::MissingConfigFile(format!("{}", path.as_ref().display())))?;
        let conf = serde_yaml::from_str(&conf)?;
        Ok(conf)
    }

    /// Gets the currently active context based on the `current-context` key in
    /// the config file.
    pub fn get_current_context(&self) -> Option<&Context> {
        if let Some(current_context) = &self.current_context {
            for context in self.contexts.iter() {
                if &context.name == current_context {
                    return Some(context);
                }
            }
        }

        None
    }
}