"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.dockerdriver = void 0;
const openflow_api_1 = require("@openiap/openflow-api");
const Logger_1 = require("./Logger");
const Crypt_1 = require("./Crypt");
const Config_1 = require("./Config");
const url = require("url");
const Docker = require("dockerode");
class dockerdriver {
    async detect() {
        try {
            const docker = new Docker();
            await docker.listContainers();
            return true;
        }
        catch (error) {
            Logger_1.Logger.instanse.error("dockerdriver", "detect", error);
        }
        return false;
    }
    async EnsureNoderedInstance(jwt, tuser, _id, name, skipcreate, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("message.EnsureNoderedInstance", parent);
        Logger_1.Logger.instanse.debug("dockerdriver", "EnsureNoderedInstance", "[" + tuser.username + "] EnsureNoderedInstance");
        if (_id === null || _id === undefined || _id === "")
            _id = tuser._id;
        const users = await Config_1.Config.db.query({ query: { _id: _id }, top: 1, collectionname: "users", jwt: jwt }, span);
        if (users.length == 0) {
            throw new Error("Unknown userid " + _id);
        }
        const user = openflow_api_1.NoderedUser.assign(users[0]);
        const docker = new Docker();
        const myhostname = require('os').hostname();
        let me = null;
        let list = await docker.listContainers({ all: 1 });
        let instance = null;
        for (let item of list) {
            var Created = new Date(item.Created * 1000);
            item.metadata = { creationTimestamp: Created, name: item.Labels["com.docker.compose.service"] };
            item.status = { phase: item.State };
            if (item.Names[0] == "/" + name) {
                instance = item;
            }
            if (item.Names[0] == "/" + myhostname || item.Id.startsWith(myhostname)) {
                me = item;
            }
            if (me == null && item.Labels["com.docker.compose.project"] == Config_1.Config.namespace) {
                me = item;
            }
        }
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(instance)) {
            let nodered_domain_schema = Config_1.Config.nodered_domain_schema;
            if (openflow_api_1.NoderedUtil.IsNullEmpty(nodered_domain_schema)) {
                nodered_domain_schema = "$nodered_id$." + Config_1.Config.domain;
            }
            const hostname = nodered_domain_schema.replace("$nodered_id$", name);
            let nodered_image_name = Config_1.Config.nodered_images[0].name;
            let tzvolume = null;
            let tz = undefined;
            if (user.nodered) {
                try {
                    if (user.nodered.api_allow_anonymous == null)
                        user.nodered.api_allow_anonymous = false;
                    if (user.nodered.function_external_modules == null)
                        user.nodered.function_external_modules = false;
                    if (user.nodered.nodered_image_name == null)
                        user.nodered.nodered_image_name = nodered_image_name;
                    if (!openflow_api_1.NoderedUtil.IsNullEmpty(user.nodered.tz)) {
                        tz = user.nodered.tz;
                        tzvolume = "/usr/share/zoneinfo/" + user.nodered.tz;
                    }
                }
                catch (error) {
                    user.nodered = { api_allow_anonymous: false, function_external_modules: false, nodered_image_name };
                }
            }
            else {
                user.nodered = { api_allow_anonymous: false, function_external_modules: false, nodered_image_name };
            }
            const _nodered_image = Config_1.Config.nodered_images.filter(x => x.name == user.nodered.nodered_image_name);
            let nodered_image = Config_1.Config.nodered_images[0].image;
            if (_nodered_image.length == 1) {
                nodered_image = _nodered_image[0].image;
            }
            let hasbilling = false;
            let assigned = await Config_1.Config.db.db.collection("config")
                .find({ "_type": "resourceusage", "userid": user._id, "resource": "Nodered Instance" }).toArray();
            if (assigned.length > 0) {
                let usage = assigned[0];
                if (usage.quantity > 0 && !openflow_api_1.NoderedUtil.IsNullEmpty(usage.siid)) {
                    hasbilling = true;
                }
            }
            const Labels = {
                "com.docker.compose.project": Config_1.Config.namespace,
                "com.docker.compose.service": Config_1.Config.namespace,
                "userid": _id,
                "billed": hasbilling.toString(),
            };
            let NetworkingConfig = undefined;
            let HostConfig = undefined;
            HostConfig = {};
            if (me != null) {
                if (me.Labels["com.docker.compose.config-hash"])
                    Labels["com.docker.compose.config-hash"] = me.Labels["com.docker.compose.config-hash"];
                if (me.Labels["com.docker.compose.project"])
                    Labels["com.docker.compose.project"] = me.Labels["com.docker.compose.project"];
                if (me.Labels["com.docker.compose.project.config_files"])
                    Labels["com.docker.compose.project.config_files"] = me.Labels["com.docker.compose.project.config_files"];
                if (me.Labels["com.docker.compose.project.working_dir"])
                    Labels["com.docker.compose.project.working_dir"] = me.Labels["com.docker.compose.project.working_dir"];
                if (me.Labels["com.docker.compose.service"])
                    Labels["com.docker.compose.service"] = me.Labels["com.docker.compose.service"];
                if (me.Labels["com.docker.compose.version"])
                    Labels["com.docker.compose.version"] = me.Labels["com.docker.compose.version"];
                if (me.NetworkSettings && me.NetworkSettings.Networks) {
                    const keys = Object.keys(me.NetworkSettings.Networks);
                    HostConfig.NetworkMode = keys[0];
                }
            }
            // docker-compose -f docker-compose-traefik.yml -p demo up -d
            Labels["traefik.enable"] = "true";
            Labels["traefik.http.routers." + name + ".entrypoints"] = Config_1.Config.nodered_docker_entrypoints;
            Labels["traefik.http.routers." + name + ".rule"] = "Host(`" + hostname + "`)";
            Labels["traefik.http.services." + name + ".loadbalancer.server.port"] = Config_1.Config.port.toString();
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.nodered_docker_certresolver)) {
                Labels["traefik.http.routers." + name + ".tls.certresolver"] = Config_1.Config.nodered_docker_certresolver;
            }
            // HostConfig.PortBindings = { "5859/tcp": [{ HostPort: '5859' }] }
            let api_ws_url = Config_1.Config.basewsurl();
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.api_ws_url))
                api_ws_url = Config_1.Config.api_ws_url;
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.nodered_ws_url))
                api_ws_url = Config_1.Config.nodered_ws_url;
            if (!api_ws_url.endsWith("/"))
                api_ws_url += "/";
            const nodereduser = await Logger_1.Logger.DBHelper.FindById(_id, jwt, span);
            const tuser = openflow_api_1.TokenUser.From(nodereduser);
            const nodered_jwt = Crypt_1.Crypt.createToken(tuser, Config_1.Config.personalnoderedtoken_expires_in);
            var saml_federation_metadata = Config_1.Config.saml_federation_metadata;
            if (saml_federation_metadata == "https://pc.openiap.io/issue/FederationMetadata/2007-06/FederationMetadata.xml") {
                saml_federation_metadata = "https://demo.openiap.io/issue/FederationMetadata/2007-06/FederationMetadata.xml";
            }
            await Logger_1.Logger.DBHelper.EnsureNoderedRoles(tuser, jwt, true, span);
            let saml_baseurl = Config_1.Config.protocol + "://" + hostname + "/";
            let _samlparsed = url.parse(saml_federation_metadata);
            if (_samlparsed.protocol == "http:" || _samlparsed.protocol == "ws:") {
                saml_baseurl = "http://" + hostname;
                if (_samlparsed.port && _samlparsed.port != "80" && _samlparsed.port != "3000") {
                    saml_baseurl += ":" + _samlparsed.port;
                }
            }
            else {
                saml_baseurl = "https://" + hostname;
                if (_samlparsed.port && _samlparsed.port != "443" && _samlparsed.port != "3000") {
                    saml_baseurl += ":" + _samlparsed.port;
                }
            }
            saml_baseurl += "/";
            // https://demo.openiap.io/issue
            // "saml_baseurl=" + saml_baseurl,
            var nodered_saml_entrypoint = saml_federation_metadata.split("/FederationMetadata/2007-06/FederationMetadata.xml").join("");
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.nodered_saml_entrypoint))
                nodered_saml_entrypoint = Config_1.Config.nodered_saml_entrypoint;
            // "saml_entrypoint=" + Config.baseurl() + 'issue',
            const Env = [
                "saml_federation_metadata=" + saml_federation_metadata,
                "saml_issuer=" + Config_1.Config.saml_issuer,
                "saml_entrypoint=" + nodered_saml_entrypoint,
                "nodered_id=" + name,
                "nodered_sa=" + nodereduser.username,
                "jwt=" + nodered_jwt,
                "queue_prefix=" + user.nodered.queue_prefix,
                "api_ws_url=" + api_ws_url,
                "domain=" + hostname,
                "protocol=" + Config_1.Config.protocol,
                "port=" + Config_1.Config.port.toString(),
                "noderedusers=" + (name + "noderedusers"),
                "noderedadmins=" + (name + "noderedadmins"),
                "noderedapiusers=" + (name + "nodered api users"),
                "api_allow_anonymous=" + user.nodered.api_allow_anonymous.toString(),
                "function_external_modules=" + user.nodered.function_external_modules.toString(),
                "prometheus_measure_nodeid=" + Config_1.Config.prometheus_measure_nodeid.toString(),
                "prometheus_measure_queued_messages=" + Config_1.Config.prometheus_measure_queued_messages.toString(),
                "NODE_ENV=" + Config_1.Config.NODE_ENV,
                "HTTP_PROXY=" + Config_1.Config.HTTP_PROXY,
                "HTTPS_PROXY=" + Config_1.Config.HTTPS_PROXY,
                "NO_PROXY=" + Config_1.Config.NO_PROXY,
                "prometheus_expose_metric=" + "false",
                "enable_analytics=" + Config_1.Config.enable_analytics.toString(),
                "tours=" + Config_1.Config.enable_nodered_tours.toString(),
                "otel_trace_url=" + Config_1.Config.otel_trace_url,
                "otel_metric_url=" + Config_1.Config.otel_metric_url,
                "otel_trace_interval=" + Config_1.Config.otel_trace_interval.toString(),
                "otel_metric_interval=" + Config_1.Config.otel_metric_interval.toString(),
                "amqp_enabled_exchange=" + Config_1.Config.amqp_enabled_exchange.toString(),
                "noderedcatalogues=" + Config_1.Config.noderedcatalogues,
                "log_with_colors=" + Config_1.Config.log_with_colors.toString(),
                "TZ=" + tz
            ];
            if (tzvolume != null) {
                HostConfig.Binds = ["/etc/localtime", tzvolume];
            }
            await this._pullImage(docker, nodered_image);
            instance = await docker.createContainer({
                Image: nodered_image, name, Labels, Env, NetworkingConfig, HostConfig
            });
            await instance.start();
        }
        else {
            const container = docker.getContainer(instance.Id);
            if (instance.State != "running") {
                container.start();
            }
        }
    }
    async GetNoderedInstance(jwt, tokenUser, _id, name, parent) {
        var _a;
        const span = Logger_1.Logger.otel.startSubSpan("message.EnsureNoderedInstance", parent);
        const rootjwt = Crypt_1.Crypt.rootToken();
        const rootuser = openflow_api_1.TokenUser.From(Crypt_1.Crypt.rootUser());
        try {
            const noderedresource = await Config_1.Config.db.GetOne({ "collectionname": "config", "query": { "name": "Nodered Instance", "_type": "resource" } }, span);
            let runtime = (_a = noderedresource === null || noderedresource === void 0 ? void 0 : noderedresource.defaultmetadata) === null || _a === void 0 ? void 0 : _a.runtime_hours;
            if (openflow_api_1.NoderedUtil.IsNullUndefinded(runtime)) {
                // If nodered resource does not exists, dont turn off nodereds
                runtime = 0;
                // If nodered resource does exists, but have no default, use 24 hours
                if (!openflow_api_1.NoderedUtil.IsNullUndefinded(noderedresource))
                    runtime = 24;
            }
            span === null || span === void 0 ? void 0 : span.addEvent("init Docker()");
            const docker = new Docker();
            span === null || span === void 0 ? void 0 : span.addEvent("listContainers()");
            var list = await docker.listContainers({ all: 1 });
            var result = [];
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                var Created = new Date(item.Created * 1000);
                item.metadata = { creationTimestamp: Created, name: item.Names[0].substr(1) };
                item.status = { phase: item.State };
                const image = item.Image;
                const userid = item.Labels["userid"];
                const billed = item.Labels["billed"];
                let deleted = false;
                if ((image.indexOf("openflownodered") > -1 || image.indexOf("openiap/nodered") > -1) && !openflow_api_1.NoderedUtil.IsNullEmpty(userid)) {
                    if (!openflow_api_1.NoderedUtil.IsNullUndefinded(noderedresource) && runtime > 0) {
                        const date = new Date();
                        const a = date - Created;
                        const diffhours = a / (1000 * 60 * 60);
                        if (billed != "true" && diffhours > runtime) {
                            Logger_1.Logger.instanse.warn("dockerdriver", "GetNoderedInstance", "[" + tokenUser.username + "] Remove un billed nodered instance " + name + " that has been running for " + diffhours + " hours");
                            await this.DeleteNoderedInstance(rootjwt, rootuser, _id, name, span);
                            deleted = true;
                        }
                    }
                    if (item.Names[0] == "/" + name && deleted == false) {
                        span === null || span === void 0 ? void 0 : span.addEvent("getContainer(" + item.Id + ")");
                        const container = docker.getContainer(item.Id);
                        span === null || span === void 0 ? void 0 : span.addEvent("stats()");
                        var stats = await container.stats({ stream: false });
                        let cpu_usage;
                        let memory;
                        let memorylimit;
                        if (stats && stats.cpu_stats && stats.cpu_stats.cpu_usage && stats.cpu_stats.cpu_usage.usage_in_usermode)
                            cpu_usage = stats.cpu_stats.cpu_usage.usage_in_usermode;
                        if (stats && stats.memory_stats && stats.memory_stats.usage)
                            memory = stats.memory_stats.usage;
                        if (stats && stats.memory_stats && stats.memory_stats.limit)
                            memorylimit = stats.memory_stats.limit;
                        item.metrics = {
                            cpu: parseFloat((cpu_usage / 1024 / 1024).toString()).toFixed(2) + "n",
                            memory: parseFloat((memory / 1024 / 1024).toString()).toFixed(2) + "Mi",
                            memorylimit: parseFloat((memorylimit / 1024 / 1024).toString()).toFixed(2) + "Mi"
                        };
                        result.push(item);
                    }
                }
            }
            return result;
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            throw new Error(error.message ? error.message : error);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async RestartNoderedInstance(jwt, tuser, _id, name, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("message.DockerRestartNoderedInstance", parent);
        try {
            span === null || span === void 0 ? void 0 : span.addEvent("init Docker()");
            const docker = new Docker();
            span === null || span === void 0 ? void 0 : span.addEvent("listContainers()");
            var list = await docker.listContainers({ all: 1 });
            var instance = null;
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                if (item.Names[0] == "/" + name) {
                    instance = item;
                }
            }
            if (instance != null) {
                span === null || span === void 0 ? void 0 : span.addEvent("getContainer(" + instance.Id + ")");
                const container = docker.getContainer(instance.Id);
                if (instance.State == "running")
                    await container.stop();
                await container.restart();
            }
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            throw new Error(error.message ? error.message : error);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    _pullImage(docker, imagename) {
        return new Promise((resolve, reject) => {
            docker.pull(imagename, function (err, stream) {
                if (err)
                    return reject(err);
                docker.modem.followProgress(stream, onFinished, onProgress);
                function onFinished(err2, output) {
                    Logger_1.Logger.instanse.debug("dockerdriver", "_pullImage", output);
                    if (err2) {
                        Logger_1.Logger.instanse.error("dockerdriver", "_pullImage", err2);
                        return reject(err2);
                    }
                    return resolve();
                }
                function onProgress(event) {
                    Logger_1.Logger.instanse.debug("dockerdriver", "_pullImage", event);
                }
            });
        });
    }
    async GetNoderedInstanceLog(jwt, user, _id, name, podname, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("message.GetNoderedInstanceLog", parent);
        try {
            var result = null;
            const docker = new Docker();
            let me = null;
            let list = await docker.listContainers({ all: 1 });
            let instance = null;
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                var Created = new Date(item.Created * 1000);
                item.metadata = { creationTimestamp: Created, name: item.Labels["com.docker.compose.service"] };
                item.status = { phase: item.State };
                if (item.Names[0] == "/" + podname) {
                    instance = item;
                }
            }
            if (instance != null) {
                var logOpts = {
                    stdout: 1,
                    stderr: 1,
                    tail: 50,
                    follow: 0
                };
                const container = docker.getContainer(instance.Id);
                var s = await container.logs(logOpts);
                result = s.toString();
            }
            if (result == null)
                result = "";
            return result;
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            throw new Error(error.message ? error.message : error);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async DeleteNoderedInstance(jwt, tokenUser, _id, name, parent) {
        this.DeleteNoderedPod(jwt, tokenUser, _id, name, null, parent);
    }
    async DeleteNoderedPod(jwt, user, _id, name, podname, parent) {
        const span = Logger_1.Logger.otel.startSubSpan("message.dockerDeleteNoderedPod", parent);
        try {
            Logger_1.Logger.instanse.debug("dockerdriver", "DeleteNoderedPod", "[" + user.username + "] dockerDeleteNoderedPod");
            if (openflow_api_1.NoderedUtil.IsNullEmpty(podname))
                podname = name;
            span === null || span === void 0 ? void 0 : span.addEvent("init Docker()");
            const docker = new Docker();
            span === null || span === void 0 ? void 0 : span.addEvent("listContainers()");
            var list = await docker.listContainers({ all: 1 });
            for (let i = 0; i < list.length; i++) {
                const item = list[i];
                if (item.Names[0] == "/" + podname) {
                    span === null || span === void 0 ? void 0 : span.addEvent("getContainer(" + item.Id + ")");
                    const container = docker.getContainer(item.Id);
                    if (item.State == "running")
                        await container.stop();
                    span === null || span === void 0 ? void 0 : span.addEvent("remove()");
                    await container.remove();
                }
            }
        }
        catch (error) {
            span === null || span === void 0 ? void 0 : span.recordException(error);
            throw new Error(error.message ? error.message : error);
        }
        finally {
            Logger_1.Logger.otel.endSpan(span);
        }
    }
    async NodeLabels(parent) {
        return null;
    }
}
exports.dockerdriver = dockerdriver;
//# sourceMappingURL=dockerdriver.js.map