cockpit/pkg/kubernetes/scripts/kube-client-cockpit.js

1265 lines
46 KiB
JavaScript

/*
* This file is part of Cockpit.
*
* Copyright (C) 2015 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var angular = require('angular');
var cockpit = require('cockpit');
require('./kube-client');
function debug() {
if (window.debugging == "all" || window.debugging == "kube")
console.debug.apply(console, arguments);
}
function updateMessage(response) {
if (!response)
return;
var obj;
try {
obj = JSON.parse(response.data);
} catch(e) {
// Some kubernetes versions message up json reponses
if (response.data && response.headers &&
response.headers["Content-Type"] === "text/plain") {
obj = { message: response.data };
} else {
return;
}
}
if (obj && obj.message)
response.message = response.statusText = obj.message;
}
/*
* Currently we assume that the certificates in the kube config
* file are:
*
* base64(PEM(data))
*
* Since our http-stream2 expects PEM certificates (although they're
* nasty, they're better than all the alternatives) so we strip out
* the outer base64 layer.
*/
function parseCertOption(object, option) {
var blob = object[option + "-data"];
if (blob !== undefined)
return { data: window.atob(blob) };
var file = object[option];
if (file !== undefined)
return { file: file };
return undefined;
}
function basicToken(user, pass) {
return window.btoa(window.unescape(encodeURIComponent(user + ":" + pass)));
}
angular.module("kubeClient.cockpit", [
"kubeClient",
])
.factory("CockpitKubeWatch", [
"$q",
"KUBE_SCHEMA",
"cockpitKubeDiscover",
function($q, KUBE_SCHEMA, cockpitKubeDiscover) {
return function CockpitKubeWatch(path, callback) {
debug("creating watch:", path);
/* Used to track the last resource for restarting query */
var lastResource;
/* Whether close has been called */
var stopping = false;
/* The current HTTP request */
var channel = null;
/* The http options */
var http = null;
/* Waiting to do the next http request */
var wait = null;
/*
* Loading logic.
*
* For performance, we only use watches here. So we have
* to guess when loading is finished and when updates begin.
* There are several heuristics:
*
* 1) Receiving a MODIFY or DELETE means loading has finished.
* 2) A timeout after last ADDED
* 3) Error or connection closed.
*
* Remember that a watch object can restart its request for a number
* of reasons, and so the loading/loaded state may go back and forth.
*
* When transitioning from a loading to a loaded state, we have to:
* a) Notify the caller if not already done
* b) See if any objects present before load need to be removed.
*/
var defer = $q.defer();
var promise = defer.promise;
var loaded = false;
var objects = { };
var previous;
var loading;
function loadBegin(full) {
if (full) {
previous = objects;
objects = { };
} else {
previous = null;
}
loadPoke(true);
}
function loadPoke(force) {
if (force || loading !== undefined) {
window.clearTimeout(loading);
loading = window.setTimeout(loadFinish, 100);
}
}
function loadFinish(ex) {
if (loaded)
return;
loaded = true;
var key, prev, frames = [];
prev = previous;
previous = null;
for (key in prev) {
if (!(key in objects))
frames.push({ type: "DELETED", object: prev[key] });
}
/* Simulated delete frames */
if (frames.length)
callback(frames);
/* Tell callback to flush */
callback([]);
if (ex)
defer.reject(ex);
else
defer.resolve();
}
/*
* Each change is sent as an individual line from Kubernetes
* but they may not arrive exactly that way, so we buffer
* and split lines again.
*/
var buffer;
function handleWatch(data) {
if (buffer)
data = buffer + data;
var lines = data.split("\n");
var i, length = lines.length - 1;
/* Last line is incomplete save for later */
buffer = lines[length];
var frames = [];
/* Process all the others */
var frame, object;
for (i = 0; i < length; i++) {
try {
frame = JSON.parse(lines[i]);
} catch (ex) {
console.warn(lines[i], ex);
channel.close();
continue;
}
object = frame.object;
if (!object) {
console.warn("invalid watch without object", frame);
continue;
}
/* The watch failed, likely due to invalid resourceVersion */
if (frame.type == "ERROR") {
if (lastResource) {
lastResource = null;
startWatch();
}
continue;
}
var meta = object.metadata;
if (!meta || (!meta.uid && object.kind !== "Project") || !object.kind) {
console.warn("invalid kube object: ", object);
continue;
}
lastResource = meta.resourceVersion;
/* We track objects here so we can restart watches */
var uid = meta.uid;
if (frame.type == "DELETED")
delete objects[uid];
else
objects[uid] = object;
debug(frame.type, object.kind, meta.uid);
frames.push(frame);
}
callback(frames);
loadPoke();
}
function startWatch() {
if (channel)
return;
var full = true;
var uri = path + "?watch=true";
/*
* If we have a last resource we can guarantee that we don't miss
* any objects or changes to objects. If we don't have one, then we
* have to list everything again. Still watch at the same time though.
*/
if (lastResource) {
uri += "&resourceVersion=" + encodeURIComponent(lastResource);
full = false;
}
/*
* As a precaution, watch must take at least 1 second
* to complete. Otherwise we could be in a tight loop here.
* eg: if the API of Kubernetes changes unpredictably.
*/
var blocked = false;
window.setTimeout(function() {
blocked = true;
}, 1000);
var opts = angular.extend({ }, http, {
path: uri,
method: "GET"
});
channel = cockpit.channel(opts);
var response = { };
var failed = false;
channel.addEventListener("control", function(ev, options) {
if (options.command == "response") {
response = options;
if (response.status > 299)
failed = true;
loadBegin(full);
}
});
channel.addEventListener("message", function(ev, payload) {
if (failed)
response.data = (response.data || "") + payload;
else
handleWatch(payload);
});
channel.addEventListener("close", function(ev, options) {
channel = null;
loading = false;
if (stopping)
return;
var msg = "watching " + path + " failed: ";
var problem = options.problem;
var status = response.status;
if (problem) {
msg += problem;
if (problem == "disconnected")
debug(msg);
else
console.warn(msg);
response.problem = problem;
response.status = 999;
loadFinish(response);
} else if (failed) {
updateMessage(response);
msg += (response.message || response.reason || status);
if (status === 404 || status === 410 || status === 403 ) {
debug(msg);
loadFinish(response);
return; /* don't try watch again if we get a 404/410/403 */
} else {
console.warn(msg);
}
loadFinish(response);
} else if (!blocked) {
console.warn("watching kube " + path + " didn't block");
} else {
startWatch();
return;
}
startWatchLater();
});
/* No http request body */
channel.control({ command: "done" });
}
function startWatchLater() {
if (!wait) {
wait = window.setTimeout(function() {
wait = null;
startWatch();
}, 5000);
}
}
$q.when(cockpitKubeDiscover(), function(options) {
http = options;
startWatch();
}, function(resp) {
loadFinish(resp);
});
promise.cancel = function cancel(ex) {
stopping = true;
var problem;
if (channel) {
if (ex)
problem = ex.problem;
channel.close(problem || "disconnected");
channel = null;
}
window.clearTimeout(wait);
wait = null;
http = null;
loadFinish(ex);
};
return promise;
};
}
])
/*
* A WebSocket factory for the kubernetes-container-terminal
* that uses the kubectl command with a fake websocket
*/
.factory("CockpitKubectlWebSocket", [
function() {
function decode(href, options) {
var pos = href.indexOf('?');
var first = href;
var path;
if (pos === -1)
first = href;
else
first = href.substr(0, pos);
path = first.split('/').map(decodeURIComponent);
if (pos !== -1) {
angular.forEach(href.substring(pos + 1).split("&"), function(opt) {
var last, parts = opt.split('=');
var name = decodeURIComponent(parts[0]);
var value = decodeURIComponent(parts[1]);
if (options.hasOwnProperty(name)) {
last = options[name];
if (!angular.isArray(last))
last = options[name] = [ last ];
last.push(value);
} else {
options[name] = value;
}
});
}
return path;
}
function parser(url) {
var options = { };
var path = decode(url, options);
var command = [ ];
var args = [ ];
var namespace = "default";
var container = null;
var cmd = "log";
var pod = "";
var i, len;
for (i = 0, len = path.length; i < len; i++) {
if (path[i] === "namespaces") {
namespace = path[++i];
} else if (path[i] === "pods") {
pod = path[++i];
if (path[i + 1] == "exec")
cmd = "exec";
else if (path[i + 1] == "log")
cmd = "logs";
}
}
for (i in options) {
if (i == "container") {
container = options[i];
} else if (i == "command") {
if (angular.isArray(options[i]))
command = options[i];
else
command.push(options[i]);
} else if (i == "stdin" || i == "tty" || i == "follow") {
args.push("--" + i);
}
}
var ret = [ "kubectl", cmd, "--namespace=" + namespace ];
if (container)
ret.push("--container=" + container);
ret.push.apply(ret, args);
ret.push(pod, "--");
ret.push.apply(ret, command);
return ret;
}
return function KubeFakeWebSocket(url, protocols) {
var cmd = parser(url);
var base64 = false;
/* A fake WebSocket */
var channel;
var state = 0; /* CONNECTING */
var ws = { };
cockpit.event_target(ws);
function open() {
channel = cockpit.channel({
payload: "stream",
spawn: cmd,
pty: true
});
channel.addEventListener("close", function(ev, options) {
var problem = options.problem || "";
channel = null;
state = 3;
var cev = document.createEvent('Event');
cev.initEvent('close', false, false, !!problem, 1000, problem);
ws.dispatchEvent(cev);
});
channel.addEventListener("message", function(ev, data) {
if (base64)
data = "1" + window.btoa(data);
var mev;
if (window.MessageEvent) {
mev = new window.MessageEvent('message', { 'data': data });
} else {
mev = document.createEvent('MessageEvent');
mev.initMessageEvent('message', false, false, data, null, null, window, null);
}
ws.dispatchEvent(mev);
});
state = 1;
var oev = document.createEvent('Event');
oev.initEvent('open', false, false);
ws.dispatchEvent(oev);
}
function fail() {
var ev = document.createEvent('Event');
ev.initEvent('close', false, false, false, 1002, "protocol-error");
ws.dispatchEvent(ev);
}
function close(code, reason) {
if (channel)
channel.close(reason);
}
function send(data) {
if (base64) {
/*
* HACK: container-terminal sends Width/Height commands but
* many of the kubernetes implementations don't yet support
* that. So filter them out here for now.
*/
if (data[0] === "4")
return;
data = window.atob(data.slice(1));
}
if (channel)
channel.send(data);
}
var valid = true;
if (protocols) {
if (angular.isArray(protocols))
valid = base64 = protocols.indexOf("base64.channel.k8s.io") !== -1;
else
valid = base64 = protocols === "base64.channel.k8s.io";
}
/* A fake WebSocket */
Object.defineProperties(ws, {
binaryType: { value: "arraybuffer" },
bufferedAmount: { value: 0 },
extensions: { value: "" },
protocol: { value: base64 ? "base64.channel.k8s.io" : "" },
readyState: { get: function() { return state; } },
url: { value: url },
close: { value: close },
send: { value: send },
});
if (valid) {
window.setTimeout(open);
} else {
console.warn("Unsupported kubernetes container WebSocket subprotocol: " + protocols);
window.setTimeout(fail);
}
return ws;
};
}
])
.factory("CockpitKubeSocket", [
"$q",
"$injector",
function($q, $injector) {
return function CockpitKubeSocket(url, config) {
var base64 = false;
var connect;
var state = 0; /* CONNECTING */
var ws = { };
var channel;
var protocols = [];
if (config && config.protocols) {
protocols = config.protocols;
if (!angular.isArray(protocols))
protocols = [ String(config.protocols) ];
}
/*
* If we're called with fully formed options, then don't do
* connect discovery stuff. Otherwise ask our connect service
* for connection info, and do discovery.
*/
if (config && config.port)
connect = config;
else
connect = $injector.get('cockpitKubeDiscover')();
function close(code, reason) {
if (channel)
channel.close(reason);
}
function send(data) {
/*
* HACK: container-terminal sends Width/Height commands but
* many of the kubernetes implementations don't yet support
* that. So filter them out here for now.
*/
if (base64 && data[0] === "4")
return;
if (channel)
channel.send(data);
}
/* A fake WebSocket */
Object.defineProperties(ws, {
binaryType: { value: "arraybuffer" },
bufferedAmount: { value: 0 },
extensions: { value: "" },
protocol: { value: protocols[0] },
readyState: { get: function() { return state; } },
url: { value: url },
close: { value: close },
send: { value: send },
});
base64 = protocols.indexOf("base64.channel.k8s.io") !== -1;
$q.when(connect, function connected(options) {
cockpit.event_target(ws);
channel = cockpit.channel(angular.extend({ }, options, {
payload: "websocket-stream1",
path: url,
protocols: protocols.length > 0 ? protocols : undefined,
}));
channel.addEventListener("close", function(ev, options) {
var problem = options.problem || "";
channel = null;
state = 3;
var cev = document.createEvent('Event');
cev.initEvent('close', false, false, !!problem, 1000, problem);
ws.dispatchEvent(cev);
});
channel.addEventListener("message", function(ev, data) {
var mev;
if (window.MessageEvent) {
mev = new window.MessageEvent('message', { 'data': data });
} else {
mev = document.createEvent('MessageEvent');
mev.initMessageEvent('message', false, false, data, null, null, window, null);
}
ws.dispatchEvent(mev);
});
state = 1;
var oev = document.createEvent('Event');
oev.initEvent('open', false, false);
ws.dispatchEvent(oev);
});
return ws;
};
}
])
.factory("CockpitKubeRequest", [
"$q",
"$injector",
function($q, $injector) {
var CONTENT_TYPE = "Content-Type";
var JSON_TYPE = "application/json";
return function CockpitKubeRequest(method, path, body, config) {
var defer = $q.defer();
var promise = defer.promise;
var connect, channel;
var heads = { };
if (body && typeof body == "object") {
body = JSON.stringify(body);
heads[CONTENT_TYPE] = JSON_TYPE;
}
if (!config)
config = { };
/*
* If we're called with fully formed options, then don't do
* connect discovery stuff. Otherwise ask our connect service
* for connection info, and do discovery.
*/
if (config && config.port)
connect = config;
else
connect = $injector.get('cockpitKubeDiscover')();
$q.when(connect, function connected(options) {
var opts = angular.extend({ }, config, options, {
path: path,
method: method,
payload: "http-stream2"
});
opts.headers = angular.extend(heads, config.headers || { }, options.headers || { });
debug("request:", method, path, opts);
channel = cockpit.channel(opts);
var response = { };
channel.addEventListener("control", function(ev, options) {
if (options.command == "response") {
response = options;
response.statusText = response.reason;
}
});
channel.addEventListener("message", function(ev, payload) {
response.data = (response.data || "") + payload;
});
channel.addEventListener("close", function(ev, options) {
channel = null;
response.options = options;
if (options.problem) {
response.problem = response.statusText = options.problem;
response.status = 999;
}
var headers = response.headers || { };
var content_type = headers[CONTENT_TYPE] || headers[CONTENT_TYPE.toLowerCase()] || "";
if (content_type.lastIndexOf(JSON_TYPE, 0) === 0) {
try {
response.data = JSON.parse(response.data);
} catch (ex) {
/* it's not JSON, just leave as text */
}
}
if (response.status > 299) {
updateMessage(response);
defer.reject(response);
} else {
defer.resolve(response);
}
});
if (body)
channel.send(body);
channel.control({ command: "done" });
/* Failed to connect */
}, function failed(response) {
defer.reject(response);
});
/* Helpful function on the promise */
promise.cancel = function cancel() {
if (connect.cancel)
connect.cancel();
if (channel)
channel.close("cancelled");
};
return promise;
};
}
])
.factory("cockpitRunCommand", [
'$q',
function($q) {
return function runCommand(args) {
var defer = $q.defer();
var promise = defer.promise;
var channel = cockpit.channel({
"payload": "stream",
"spawn": args,
"err": "message"
});
var result = "";
channel.addEventListener("message", function(ev, payload) {
result += payload;
});
channel.addEventListener("close", function(ev, options) {
channel = null;
if (options.problem)
defer.reject(options);
else
defer.resolve(result);
});
promise.cancel = function cancel(options) {
if (channel)
channel.close(options || "cancelled");
};
promise.send = function send(data) {
if (channel)
channel.send(data);
};
return promise;
};
}
])
.factory("cockpitKubectlConfig", [
'$q',
'cockpitRunCommand',
function($q, runCommand) {
function generateKubeOptions(cluster, user) {
var parser, token, provider;
var options = { port: 8080, headers: { } };
if (cluster && cluster.server) {
parser = document.createElement('a');
parser.href = cluster.server;
if (parser.hostname)
options.address = parser.hostname;
if (parser.port)
options.port = parseInt(parser.port, 10);
if (parser.protocol == 'https:') {
if (!parser.port || parser.port === "0")
options.port = parser.href == cluster.server ? 6443 : 443;
options.tls = { };
options.tls.authority = parseCertOption(cluster, "certificate-authority");
options.tls.validate = !cluster["insecure-skip-tls-verify"];
}
}
if (user) {
token = user.token;
provider = user["auth-provider"] || {};
if (provider.config) {
if (provider.config['access-token'])
token = provider.config['access-token'];
else if (provider.config['token'])
token = provider.config['token'];
}
if (token)
options.headers["Authorization"] = "Bearer " + token;
if (user.username)
options.headers["Authorization"] = "Basic " + basicToken(user.username, user.password || "");
if (options.tls) {
options.tls.certificate = parseCertOption(user, "client-certificate");
options.tls.key = parseCertOption(user, "client-key");
}
}
return options;
}
function parseKubeConfig(data, contextName) {
var config, options;
try {
config = JSON.parse(data);
} catch(ex) {
console.warn("received invalid kubectl config", ex);
return null;
}
if (!contextName)
contextName = config["current-context"];
var contexts = config["contexts"] || [];
/* Find the cluster info */
var userName, clusterName;
contexts.forEach(function(info) {
if (info.name === contextName) {
var context = info.context || { };
userName = context.user;
clusterName = context.cluster;
}
});
/* Find the user info */
var user, users = config["users"] || [];
users.forEach(function(info) {
if (info.name === userName)
user = info.user;
});
/* Find the cluster info */
var cluster, clusters = config["clusters"] || [];
clusters.forEach(function(info) {
if (info.name == clusterName)
cluster = info.cluster;
});
options = generateKubeOptions(cluster, user);
debug("parsed kube config", options);
return options;
}
function read() {
var cmd = ["kubectl", "config", "view", "--output=json", "--raw"];
/* Call kubectl minified config view. That only outputs
* the objects that would be used by a connection */
return runCommand(["kubectl", "config", "view", "--minify", "--output=json"])
.then(function (data) {
var p;
var auth_provider;
var user;
/* If the default data has a 'auth-provider'
* section then call kubectl version to try to
* get it to fill in any token data.
*/
try {
user = JSON.parse(data)["users"][0];
if (user && user['user'])
auth_provider = user['user']['auth-provider'];
} catch(ex) {
console.warn("received invalid kubectl config", ex);
}
if (auth_provider) {
p = runCommand(["kubectl", "version"])
.then(function () {
return runCommand(cmd);
}, function () {
return runCommand(cmd);
});
} else {
p = runCommand(cmd);
}
return p;
});
}
return {
read: read,
parseKubeConfig: parseKubeConfig,
generateKubeOptions: generateKubeOptions,
};
}
])
.factory("cockpitKubeDiscover", [
"$q",
"CockpitKubeRequest",
"cockpitKubectlConfig",
"cockpitConnectionInfo",
"cockpitBrowserStorage",
function($q, CockpitKubeRequest, cockpitKubectlConfig, info, browser) {
var defer = null;
return function cockpitKubeDiscover(force) {
if (!force && defer)
return defer.promise;
var last, req, kubectl, loginOptions;
var loginData = browser.localStorage.getItem('login-data', true);
defer = $q.defer();
var schemes = [
{ port: 8080 },
{ port: 8443, tls: { } },
{ port: 6443, tls: { } },
];
function step(options, kubeConfig) {
if (!options)
options = schemes.shift();
/* No further ports to try? */
if (!options) {
last.statusText = cockpit.gettext("Couldn't find running API server");
last.problem = "not-found";
defer.reject(last);
return;
}
/* If options is a function call it, the function is
* responsible to call step again when ready */
if (typeof options === "function") {
options();
return;
}
options.payload = "http-stream2";
debug("trying kube at:", options);
req = new CockpitKubeRequest("GET", "/api", "", options);
req.then(function(response) {
req = null;
var resp = response.data;
if (resp && resp.versions) {
debug("discovered kube api", resp);
if (kubeConfig) {
info.kubeConfig = kubeConfig;
if (kubectl)
info.type = "kubectl";
else
info.type = "sessionData";
} else {
info.type = "open";
}
defer.resolve(options);
} else {
debug("not an api endpoint:", options);
last = response;
kubectl = null;
step();
}
})
.catch(function(response) {
req = null;
kubectl = null;
last = response;
if (response.problem === "not-found") {
debug("api endpoint not found on:", options);
step();
} else {
debug("connecting to kube failed:", response);
defer.reject(response);
}
});
}
function kubectlStep() {
kubectl = cockpitKubectlConfig.read()
.then(function(data) {
var options = cockpitKubectlConfig.parseKubeConfig(data);
step(options, options ? JSON.parse(data) : null);
})
.catch(function(options) {
console.warn("kubectl failed: " + (options.message || options.problem));
step();
});
}
if (force && typeof force === 'object') {
schemes = [ force ];
step();
} else if (force === "kubectl") {
schemes = [ kubectlStep ];
step();
} else {
schemes.unshift(kubectlStep);
if (loginData)
loginOptions = cockpitKubectlConfig.parseKubeConfig(loginData);
step(loginOptions, loginOptions ? JSON.parse(loginData) : null);
}
defer.promise.cancel = function cancel() {
if (kubectl && kubectl.cancel)
kubectl.cancel("cancelled");
if (req)
req.close("cancelled");
};
return defer.promise;
};
}
])
.factory("CockpitEnvironment", [
"$q",
function($q) {
var defer = $q.defer();
var settings = null;
return function cockpitKubeSettings() {
if (settings !== null)
return defer.promise;
var channel = cockpit.channel({ payload: "dbus-json3", bus: "internal" });
channel.addEventListener("close", function(ev, options) {
if (options.problem) {
console.warn("couldn't retrieve environment:", options.problem);
defer.reject(options);
} else {
defer.resolve(settings);
}
});
channel.addEventListener("message", function(ev, data) {
var result = JSON.parse(data);
if (result.reply) {
settings = result.reply[0][0].Variables.v;
channel.close(null);
} else if (result.error) {
console.warn("error retrieving environment:", result.error);
channel.close("internal-error");
}
});
channel.send(JSON.stringify({
id: "cookie",
call: [ "/environment", "org.freedesktop.DBus.Properties", "GetAll",
[ "cockpit.Environment" ] ]
}));
return defer.promise;
};
}
])
.factory("cockpitKubeDiscoverSettings", [
"$q",
"CockpitKubeRequest",
"cockpitKubeDiscover",
"CockpitEnvironment",
'kubeLoader',
'cockpitConnectionInfo',
'KubevirtPrefix',
function($q, CockpitKubeRequest, cockpitKubeDiscover,
CockpitEnvironment, loader, info, kubevirtPrefix) {
var promise = null;
return function kubeDiscoverSettings(force) {
if (!force && promise)
return promise;
var settings = {
registry: {
host: "registry",
host_explicit: false
},
flavor: "kubernetes",
isAdmin: false,
currentUser: null,
canChangeConnection: false,
kubevirtEnabled: false,
};
var env_p = CockpitEnvironment()
.then(function(result) {
var regHost = result["REGISTRY_HOST"];
if (regHost) {
settings.registry.host = regHost;
settings.registry.host_explicit = true;
}
var openshifthost = result["OPENSHIFT_OAUTH_PROVIDER_URL"];
if (openshifthost) {
settings.registry.openshifthost = openshifthost.replace(/^http(s?):\/\//i, "");
settings.registry.openshifthost_explicit = true;
}
}, function(ex) {});
var discover_p = cockpitKubeDiscover(force)
.then(function(options) {
var req = new CockpitKubeRequest("GET", "/oapi/v1/users/~", "", options)
.then(function(response) {
settings.flavor = "openshift";
if (response)
settings.currentUser = response.data;
}, function () {
settings.flavor = "kubernetes";
});
var watch = loader.watch("namespaces")
.then(function () {
settings.isAdmin = true;
},function () {
settings.isAdmin = false;
});
var authorization;
/* See if we have a bearer token to use */
if (options.headers) {
authorization = (options.headers['Authorization'] || "").trim();
if (authorization.toLowerCase().indexOf("bearer ") === 0)
settings.registry.password = authorization.substr(7).trim();
}
return $q.all([watch, req]);
});
var kubevirt_p = new CockpitKubeRequest('GET', kubevirtPrefix)
.then(function(response) {
settings.kubevirtEnabled = true;
}, function(errorResponse) {
if (errorResponse.status === 404) {
settings.kubevirtEnabled = false;
return;
}
debug('Kubevirt check request failed:', errorResponse);
});
promise = $q.all([discover_p, env_p, kubevirt_p])
.then(function() {
settings.canChangeConnection = info.type == "kubectl";
return settings;
});
return promise;
};
}
])
.factory("cockpitBrowserStorage", [
"$window",
function($window) {
var base = $window;
if (cockpit && cockpit.sessionStorage && cockpit.localStorage)
base = cockpit;
/* Some browsers fail localStorage access due to corruption, preventing Cockpit login*/
var localStorage;
try {
localStorage = base.localStorage;
} catch(ex) {
localStorage = base.sessionStorage;
console.warn(String(ex));
}
return {
sessionStorage: base.sessionStorage,
localStorage: localStorage
};
}
])
.factory('cockpitConnectionInfo', function () {
return {
type: null,
kubeConfig: null,
};
})
.factory('cockpitContainerWebSocket', [
'CockpitKubeSocket',
'CockpitKubectlWebSocket',
'cockpitConnectionInfo',
function (socket, kubectlSocket, info) {
return function(url, protocols) {
/* config retrieved from kubectl? */
if (info.type == "kubectl")
return kubectlSocket(url, protocols);
else
return socket(url, { protocols: protocols });
};
}
])
.factory('CockpitFormat', function() {
return {
formatBytes: cockpit.format_bytes,
formatBitsPerSec: cockpit.format_bits_per_sec,
format: cockpit.format
};
})
.factory('CockpitMetrics', function() {
return {
grid: cockpit.grid,
series: cockpit.series,
};
})
.factory('CockpitTranslate', function() {
return {
gettext: cockpit.gettext,
ngettext: cockpit.ngettext,
};
});
}());