"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 child_process = require("child_process");
const fs = require("fs");
const yaml = require("js-yaml");
const net = require("net");
const path = require("path");
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_delayed_1 = require("./oidc_auth_delayed");
// fs.existsSync was removed in node 10
function fileExists(filepath) {
    try {
        fs.accessSync(filepath);
        return true;
    }
    catch (ignore) {
        return false;
    }
}
class KubeConfig {
    constructor() {
        this.authenticators = [
            new azure_auth_1.AzureAuth(),
            new gcp_auth_1.GoogleCloudPlatformAuth(),
            new exec_auth_1.ExecAuth(),
            new file_auth_1.FileAuth(),
            new oidc_auth_delayed_1.DelayedOpenIDConnectAuth(),
        ];
        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).toString('utf-8'), opts);
        this.makePathsAbsolute(rootDirectory);
    }
    async applyToHTTPSOptions(opts) {
        await this.applyOptions(opts);
        const user = this.getCurrentUser();
        if (user && user.username) {
            // The ws docs say that it accepts anything that https.RequestOptions accepts,
            // but Typescript doesn't understand that idea (yet) probably could be fixed in
            // the typings, but for now just cast to any
            opts.auth = `${user.username}:${user.password}`;
        }
        const cluster = this.getCurrentCluster();
        if (cluster && cluster.tlsServerName) {
            // The ws docs say that it accepts anything that https.RequestOptions accepts,
            // but Typescript doesn't understand that idea (yet) probably could be fixed in
            // the typings, but for now just cast to any
            opts.servername = cluster.tlsServerName;
        }
    }
    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,
            };
        }
        if (cluster && cluster.tlsServerName) {
            opts.agentOptions = { servername: cluster.tlsServerName };
        }
    }
    loadFromString(config, opts) {
        const obj = yaml.load(config);
        this.clusters = (0, config_types_1.newClusters)(obj.clusters, opts);
        this.contexts = (0, config_types_1.newContexts)(obj.contexts, opts);
        this.users = (0, 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).toString('utf-8');
        }
        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 = [];
        }
        if (this.clusters.some((c) => c.name === cluster.name)) {
            throw new Error(`Duplicate cluster: ${cluster.name}`);
        }
        this.clusters.push(cluster);
    }
    addUser(user) {
        if (!this.users) {
            this.users = [];
        }
        if (this.users.some((c) => c.name === user.name)) {
            throw new Error(`Duplicate user: ${user.name}`);
        }
        this.users.push(user);
    }
    addContext(ctx) {
        if (!this.contexts) {
            this.contexts = [];
        }
        if (this.contexts.some((c) => c.name === ctx.name)) {
            throw new Error(`Duplicate context: ${ctx.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') {
            try {
                const envKubeconfigPathResult = child_process.spawnSync('wsl.exe', [
                    'bash',
                    '-c',
                    'printenv KUBECONFIG',
                ]);
                if (envKubeconfigPathResult.status === 0 && envKubeconfigPathResult.stdout.length > 0) {
                    const result = child_process.spawnSync('wsl.exe', [
                        'cat',
                        envKubeconfigPathResult.stdout.toString('utf8'),
                    ]);
                    if (result.status === 0) {
                        this.loadFromString(result.stdout.toString('utf8'), opts);
                        return;
                    }
                }
            }
            catch (err) {
                // Falling back to default kubeconfig
            }
            try {
                const configResult = child_process.spawnSync('wsl.exe', ['cat', '~/.kube/config']);
                if (configResult.status === 0) {
                    this.loadFromString(configResult.stdout.toString('utf8'), opts);
                    const result = child_process.spawnSync('wsl.exe', ['wslpath', '-w', '~/.kube']);
                    if (result.status === 0) {
                        this.makePathsAbsolute(result.stdout.toString('utf8'));
                    }
                    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 = this.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;
// 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;
function dropDuplicatesAndNils(a) {
    return a.reduce((acceptedValues, currentValue) => {
        // Good-enough algorithm for reducing a small (3 items at this point) array into an ordered list
        // of unique non-empty strings.
        if (currentValue && !acceptedValues.includes(currentValue)) {
            return acceptedValues.concat(currentValue);
        }
        else {
            return acceptedValues;
        }
    }, []);
}
// Only public for testing.
function findHomeDir() {
    if (process.platform !== 'win32') {
        if (process.env.HOME) {
            try {
                fs.accessSync(process.env.HOME);
                return process.env.HOME;
                // tslint:disable-next-line:no-empty
            }
            catch (ignore) { }
        }
        return null;
    }
    // $HOME is always favoured, but the k8s go-client prefers the other two env vars
    // differently depending on whether .kube/config exists or not.
    const homeDrivePath = process.env.HOMEDRIVE && process.env.HOMEPATH
        ? path.join(process.env.HOMEDRIVE, process.env.HOMEPATH)
        : '';
    const homePath = process.env.HOME || '';
    const userProfile = process.env.USERPROFILE || '';
    const favourHomeDrivePathList = dropDuplicatesAndNils([homePath, homeDrivePath, userProfile]);
    const favourUserProfileList = dropDuplicatesAndNils([homePath, userProfile, homeDrivePath]);
    // 1. the first of %HOME%, %HOMEDRIVE%%HOMEPATH%, %USERPROFILE% containing a `.kube\config` file is returned.
    for (const dir of favourHomeDrivePathList) {
        try {
            fs.accessSync(path.join(dir, '.kube', 'config'));
            return dir;
            // tslint:disable-next-line:no-empty
        }
        catch (ignore) { }
    }
    // 2. ...the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists and is writeable is returned
    for (const dir of favourUserProfileList) {
        try {
            fs.accessSync(dir, fs.constants.W_OK);
            return dir;
            // tslint:disable-next-line:no-empty
        }
        catch (ignore) { }
    }
    // 3. ...the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists is returned.
    for (const dir of favourUserProfileList) {
        try {
            fs.accessSync(dir);
            return dir;
            // tslint:disable-next-line:no-empty
        }
        catch (ignore) { }
    }
    // 4. if none of those locations exists, the first of
    // %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that is set is returned.
    return favourUserProfileList[0] || 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