474 lines
15 KiB
JavaScript
474 lines
15 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.findObject = exports.findHomeDir = exports.bufferFromFileOrString = exports.makeAbsolutePath = exports.Config = exports.KubeConfig = void 0;
|
|
const tslib_1 = require("tslib");
|
|
const execa = require("execa");
|
|
const fs = require("fs");
|
|
const yaml = require("js-yaml");
|
|
const net = require("net");
|
|
const path = require("path");
|
|
const shelljs = require("shelljs");
|
|
const api = tslib_1.__importStar(require("./api"));
|
|
const azure_auth_1 = require("./azure_auth");
|
|
const config_types_1 = require("./config_types");
|
|
const exec_auth_1 = require("./exec_auth");
|
|
const file_auth_1 = require("./file_auth");
|
|
const gcp_auth_1 = require("./gcp_auth");
|
|
const oidc_auth_1 = require("./oidc_auth");
|
|
// fs.existsSync was removed in node 10
|
|
function fileExists(filepath) {
|
|
try {
|
|
fs.accessSync(filepath);
|
|
return true;
|
|
}
|
|
catch (ignore) {
|
|
return false;
|
|
}
|
|
}
|
|
class KubeConfig {
|
|
constructor() {
|
|
this.contexts = [];
|
|
this.clusters = [];
|
|
this.users = [];
|
|
}
|
|
getContexts() {
|
|
return this.contexts;
|
|
}
|
|
getClusters() {
|
|
return this.clusters;
|
|
}
|
|
getUsers() {
|
|
return this.users;
|
|
}
|
|
getCurrentContext() {
|
|
return this.currentContext;
|
|
}
|
|
setCurrentContext(context) {
|
|
this.currentContext = context;
|
|
}
|
|
getContextObject(name) {
|
|
if (!this.contexts) {
|
|
return null;
|
|
}
|
|
return findObject(this.contexts, name, 'context');
|
|
}
|
|
getCurrentCluster() {
|
|
const context = this.getCurrentContextObject();
|
|
if (!context) {
|
|
return null;
|
|
}
|
|
return this.getCluster(context.cluster);
|
|
}
|
|
getCluster(name) {
|
|
return findObject(this.clusters, name, 'cluster');
|
|
}
|
|
getCurrentUser() {
|
|
const ctx = this.getCurrentContextObject();
|
|
if (!ctx) {
|
|
return null;
|
|
}
|
|
return this.getUser(ctx.user);
|
|
}
|
|
getUser(name) {
|
|
return findObject(this.users, name, 'user');
|
|
}
|
|
loadFromFile(file, opts) {
|
|
const rootDirectory = path.dirname(file);
|
|
this.loadFromString(fs.readFileSync(file, 'utf8'), opts);
|
|
this.makePathsAbsolute(rootDirectory);
|
|
}
|
|
async applytoHTTPSOptions(opts) {
|
|
const user = this.getCurrentUser();
|
|
await this.applyOptions(opts);
|
|
if (user && user.username) {
|
|
opts.auth = `${user.username}:${user.password}`;
|
|
}
|
|
}
|
|
async applyToRequest(opts) {
|
|
const cluster = this.getCurrentCluster();
|
|
const user = this.getCurrentUser();
|
|
await this.applyOptions(opts);
|
|
if (cluster && cluster.skipTLSVerify) {
|
|
opts.strictSSL = false;
|
|
}
|
|
if (user && user.username) {
|
|
opts.auth = {
|
|
password: user.password,
|
|
username: user.username,
|
|
};
|
|
}
|
|
}
|
|
loadFromString(config, opts) {
|
|
const obj = yaml.load(config);
|
|
this.clusters = config_types_1.newClusters(obj.clusters, opts);
|
|
this.contexts = config_types_1.newContexts(obj.contexts, opts);
|
|
this.users = config_types_1.newUsers(obj.users, opts);
|
|
this.currentContext = obj['current-context'];
|
|
}
|
|
loadFromOptions(options) {
|
|
this.clusters = options.clusters;
|
|
this.contexts = options.contexts;
|
|
this.users = options.users;
|
|
this.currentContext = options.currentContext;
|
|
}
|
|
loadFromClusterAndUser(cluster, user) {
|
|
this.clusters = [cluster];
|
|
this.users = [user];
|
|
this.currentContext = 'loaded-context';
|
|
this.contexts = [
|
|
{
|
|
cluster: cluster.name,
|
|
user: user.name,
|
|
name: this.currentContext,
|
|
},
|
|
];
|
|
}
|
|
loadFromCluster(pathPrefix = '') {
|
|
const host = process.env.KUBERNETES_SERVICE_HOST;
|
|
const port = process.env.KUBERNETES_SERVICE_PORT;
|
|
const clusterName = 'inCluster';
|
|
const userName = 'inClusterUser';
|
|
const contextName = 'inClusterContext';
|
|
let scheme = 'https';
|
|
if (port === '80' || port === '8080' || port === '8001') {
|
|
scheme = 'http';
|
|
}
|
|
// Wrap raw IPv6 addresses in brackets.
|
|
let serverHost = host;
|
|
if (host && net.isIPv6(host)) {
|
|
serverHost = `[${host}]`;
|
|
}
|
|
this.clusters = [
|
|
{
|
|
name: clusterName,
|
|
caFile: `${pathPrefix}${Config.SERVICEACCOUNT_CA_PATH}`,
|
|
server: `${scheme}://${serverHost}:${port}`,
|
|
skipTLSVerify: false,
|
|
},
|
|
];
|
|
this.users = [
|
|
{
|
|
name: userName,
|
|
authProvider: {
|
|
name: 'tokenFile',
|
|
config: {
|
|
tokenFile: `${pathPrefix}${Config.SERVICEACCOUNT_TOKEN_PATH}`,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
const namespaceFile = `${pathPrefix}${Config.SERVICEACCOUNT_NAMESPACE_PATH}`;
|
|
let namespace;
|
|
if (fileExists(namespaceFile)) {
|
|
namespace = fs.readFileSync(namespaceFile, 'utf8');
|
|
}
|
|
this.contexts = [
|
|
{
|
|
cluster: clusterName,
|
|
name: contextName,
|
|
user: userName,
|
|
namespace,
|
|
},
|
|
];
|
|
this.currentContext = contextName;
|
|
}
|
|
mergeConfig(config, preserveContext = false) {
|
|
if (!preserveContext) {
|
|
this.currentContext = config.currentContext;
|
|
}
|
|
config.clusters.forEach((cluster) => {
|
|
this.addCluster(cluster);
|
|
});
|
|
config.users.forEach((user) => {
|
|
this.addUser(user);
|
|
});
|
|
config.contexts.forEach((ctx) => {
|
|
this.addContext(ctx);
|
|
});
|
|
}
|
|
addCluster(cluster) {
|
|
if (!this.clusters) {
|
|
this.clusters = [];
|
|
}
|
|
this.clusters.forEach((c, ix) => {
|
|
if (c.name === cluster.name) {
|
|
throw new Error(`Duplicate cluster: ${c.name}`);
|
|
}
|
|
});
|
|
this.clusters.push(cluster);
|
|
}
|
|
addUser(user) {
|
|
if (!this.users) {
|
|
this.users = [];
|
|
}
|
|
this.users.forEach((c, ix) => {
|
|
if (c.name === user.name) {
|
|
throw new Error(`Duplicate user: ${c.name}`);
|
|
}
|
|
});
|
|
this.users.push(user);
|
|
}
|
|
addContext(ctx) {
|
|
if (!this.contexts) {
|
|
this.contexts = [];
|
|
}
|
|
this.contexts.forEach((c, ix) => {
|
|
if (c.name === ctx.name) {
|
|
throw new Error(`Duplicate context: ${c.name}`);
|
|
}
|
|
});
|
|
this.contexts.push(ctx);
|
|
}
|
|
loadFromDefault(opts, contextFromStartingConfig = false) {
|
|
if (process.env.KUBECONFIG && process.env.KUBECONFIG.length > 0) {
|
|
const files = process.env.KUBECONFIG.split(path.delimiter).filter((filename) => filename);
|
|
this.loadFromFile(files[0], opts);
|
|
for (let i = 1; i < files.length; i++) {
|
|
const kc = new KubeConfig();
|
|
kc.loadFromFile(files[i], opts);
|
|
this.mergeConfig(kc, contextFromStartingConfig);
|
|
}
|
|
return;
|
|
}
|
|
const home = findHomeDir();
|
|
if (home) {
|
|
const config = path.join(home, '.kube', 'config');
|
|
if (fileExists(config)) {
|
|
this.loadFromFile(config, opts);
|
|
return;
|
|
}
|
|
}
|
|
if (process.platform === 'win32' && shelljs.which('wsl.exe')) {
|
|
try {
|
|
const envKubeconfigPathResult = execa.sync('wsl.exe', ['bash', '-ic', 'printenv KUBECONFIG']);
|
|
if (envKubeconfigPathResult.exitCode === 0 && envKubeconfigPathResult.stdout.length > 0) {
|
|
const result = execa.sync('wsl.exe', ['cat', envKubeconfigPathResult.stdout]);
|
|
if (result.exitCode === 0) {
|
|
this.loadFromString(result.stdout, opts);
|
|
return;
|
|
}
|
|
if (result.exitCode === 0) {
|
|
this.loadFromString(result.stdout, opts);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
// Falling back to default kubeconfig
|
|
}
|
|
try {
|
|
const result = execa.sync('wsl.exe', ['cat', '~/.kube/config']);
|
|
if (result.exitCode === 0) {
|
|
this.loadFromString(result.stdout, opts);
|
|
return;
|
|
}
|
|
}
|
|
catch (err) {
|
|
// Falling back to alternative auth
|
|
}
|
|
}
|
|
if (fileExists(Config.SERVICEACCOUNT_TOKEN_PATH)) {
|
|
this.loadFromCluster();
|
|
return;
|
|
}
|
|
this.loadFromClusterAndUser({ name: 'cluster', server: 'http://localhost:8080' }, { name: 'user' });
|
|
}
|
|
makeApiClient(apiClientType) {
|
|
const cluster = this.getCurrentCluster();
|
|
if (!cluster) {
|
|
throw new Error('No active cluster!');
|
|
}
|
|
const apiClient = new apiClientType(cluster.server);
|
|
apiClient.setDefaultAuthentication(this);
|
|
return apiClient;
|
|
}
|
|
makePathsAbsolute(rootDirectory) {
|
|
this.clusters.forEach((cluster) => {
|
|
if (cluster.caFile) {
|
|
cluster.caFile = makeAbsolutePath(rootDirectory, cluster.caFile);
|
|
}
|
|
});
|
|
this.users.forEach((user) => {
|
|
if (user.certFile) {
|
|
user.certFile = makeAbsolutePath(rootDirectory, user.certFile);
|
|
}
|
|
if (user.keyFile) {
|
|
user.keyFile = makeAbsolutePath(rootDirectory, user.keyFile);
|
|
}
|
|
});
|
|
}
|
|
exportConfig() {
|
|
const configObj = {
|
|
apiVersion: 'v1',
|
|
kind: 'Config',
|
|
clusters: this.clusters.map(config_types_1.exportCluster),
|
|
users: this.users.map(config_types_1.exportUser),
|
|
contexts: this.contexts.map(config_types_1.exportContext),
|
|
preferences: {},
|
|
'current-context': this.getCurrentContext(),
|
|
};
|
|
return JSON.stringify(configObj);
|
|
}
|
|
getCurrentContextObject() {
|
|
return this.getContextObject(this.currentContext);
|
|
}
|
|
applyHTTPSOptions(opts) {
|
|
const cluster = this.getCurrentCluster();
|
|
const user = this.getCurrentUser();
|
|
if (!user) {
|
|
return;
|
|
}
|
|
if (cluster != null && cluster.skipTLSVerify) {
|
|
opts.rejectUnauthorized = false;
|
|
}
|
|
const ca = cluster != null ? bufferFromFileOrString(cluster.caFile, cluster.caData) : null;
|
|
if (ca) {
|
|
opts.ca = ca;
|
|
}
|
|
const cert = bufferFromFileOrString(user.certFile, user.certData);
|
|
if (cert) {
|
|
opts.cert = cert;
|
|
}
|
|
const key = bufferFromFileOrString(user.keyFile, user.keyData);
|
|
if (key) {
|
|
opts.key = key;
|
|
}
|
|
}
|
|
async applyAuthorizationHeader(opts) {
|
|
const user = this.getCurrentUser();
|
|
if (!user) {
|
|
return;
|
|
}
|
|
const authenticator = KubeConfig.authenticators.find((elt) => {
|
|
return elt.isAuthProvider(user);
|
|
});
|
|
if (!opts.headers) {
|
|
opts.headers = {};
|
|
}
|
|
if (authenticator) {
|
|
await authenticator.applyAuthentication(user, opts);
|
|
}
|
|
if (user.token) {
|
|
opts.headers.Authorization = `Bearer ${user.token}`;
|
|
}
|
|
}
|
|
async applyOptions(opts) {
|
|
this.applyHTTPSOptions(opts);
|
|
await this.applyAuthorizationHeader(opts);
|
|
}
|
|
}
|
|
exports.KubeConfig = KubeConfig;
|
|
KubeConfig.authenticators = [
|
|
new azure_auth_1.AzureAuth(),
|
|
new gcp_auth_1.GoogleCloudPlatformAuth(),
|
|
new exec_auth_1.ExecAuth(),
|
|
new file_auth_1.FileAuth(),
|
|
new oidc_auth_1.OpenIDConnectAuth(),
|
|
];
|
|
// This class is deprecated and will eventually be removed.
|
|
class Config {
|
|
static fromFile(filename) {
|
|
return Config.apiFromFile(filename, api.CoreV1Api);
|
|
}
|
|
static fromCluster() {
|
|
return Config.apiFromCluster(api.CoreV1Api);
|
|
}
|
|
static defaultClient() {
|
|
return Config.apiFromDefaultClient(api.CoreV1Api);
|
|
}
|
|
static apiFromFile(filename, apiClientType) {
|
|
const kc = new KubeConfig();
|
|
kc.loadFromFile(filename);
|
|
return kc.makeApiClient(apiClientType);
|
|
}
|
|
static apiFromCluster(apiClientType) {
|
|
const kc = new KubeConfig();
|
|
kc.loadFromCluster();
|
|
const cluster = kc.getCurrentCluster();
|
|
if (!cluster) {
|
|
throw new Error('No active cluster!');
|
|
}
|
|
const k8sApi = new apiClientType(cluster.server);
|
|
k8sApi.setDefaultAuthentication(kc);
|
|
return k8sApi;
|
|
}
|
|
static apiFromDefaultClient(apiClientType) {
|
|
const kc = new KubeConfig();
|
|
kc.loadFromDefault();
|
|
return kc.makeApiClient(apiClientType);
|
|
}
|
|
}
|
|
exports.Config = Config;
|
|
Config.SERVICEACCOUNT_ROOT = '/var/run/secrets/kubernetes.io/serviceaccount';
|
|
Config.SERVICEACCOUNT_CA_PATH = Config.SERVICEACCOUNT_ROOT + '/ca.crt';
|
|
Config.SERVICEACCOUNT_TOKEN_PATH = Config.SERVICEACCOUNT_ROOT + '/token';
|
|
Config.SERVICEACCOUNT_NAMESPACE_PATH = Config.SERVICEACCOUNT_ROOT + '/namespace';
|
|
function makeAbsolutePath(root, file) {
|
|
if (!root || path.isAbsolute(file)) {
|
|
return file;
|
|
}
|
|
return path.join(root, file);
|
|
}
|
|
exports.makeAbsolutePath = makeAbsolutePath;
|
|
// This is public really only for testing.
|
|
function bufferFromFileOrString(file, data) {
|
|
if (file) {
|
|
return fs.readFileSync(file);
|
|
}
|
|
if (data) {
|
|
return Buffer.from(data, 'base64');
|
|
}
|
|
return null;
|
|
}
|
|
exports.bufferFromFileOrString = bufferFromFileOrString;
|
|
// Only public for testing.
|
|
function findHomeDir() {
|
|
if (process.env.HOME) {
|
|
try {
|
|
fs.accessSync(process.env.HOME);
|
|
return process.env.HOME;
|
|
// tslint:disable-next-line:no-empty
|
|
}
|
|
catch (ignore) { }
|
|
}
|
|
if (process.platform !== 'win32') {
|
|
return null;
|
|
}
|
|
if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
|
|
const dir = path.join(process.env.HOMEDRIVE, process.env.HOMEPATH);
|
|
try {
|
|
fs.accessSync(dir);
|
|
return dir;
|
|
// tslint:disable-next-line:no-empty
|
|
}
|
|
catch (ignore) { }
|
|
}
|
|
if (process.env.USERPROFILE) {
|
|
try {
|
|
fs.accessSync(process.env.USERPROFILE);
|
|
return process.env.USERPROFILE;
|
|
// tslint:disable-next-line:no-empty
|
|
}
|
|
catch (ignore) { }
|
|
}
|
|
return null;
|
|
}
|
|
exports.findHomeDir = findHomeDir;
|
|
// Only really public for testing...
|
|
function findObject(list, name, key) {
|
|
if (!list) {
|
|
return null;
|
|
}
|
|
for (const obj of list) {
|
|
if (obj.name === name) {
|
|
if (obj[key]) {
|
|
obj[key].name = name;
|
|
return obj[key];
|
|
}
|
|
return obj;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
exports.findObject = findObject;
|
|
//# sourceMappingURL=config.js.map
|