"use strict";
/*
 * Copyright 2018-2020 Unbounded Systems, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = tslib_1.__importStar(require("@adpt/core"));
const utils_1 = require("@adpt/utils");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const util_1 = require("util");
const common_1 = require("./common");
const Container_1 = require("./Container");
const k8s_observer_1 = require("./k8s_observer");
const manifest_support_1 = require("./manifest_support");
const Resource_1 = require("./Resource");
function isContainerArray(children) {
    const badKids = children.filter((c) => !core_1.isElement(c) || !Container_1.isK8sContainerElement(c));
    if (badKids.length === 0)
        return true;
    const names = badKids.map((c) => core_1.isElement(c) ? c.componentName : String(c));
    throw new core_1.BuildNotImplemented(`Pod children must be of type ` +
        `${Container_1.K8sContainer.name}. Invalid children are: ${names.join(", ")}`);
}
function dups(data) {
    const grouped = lodash_1.default.groupBy(data, lodash_1.default.identity);
    const filtered = lodash_1.default.filter(grouped, (val) => val.length > 1);
    return lodash_1.default.uniq(lodash_1.default.flatten(filtered));
}
function defaultize(spec) {
    spec = Object.assign({}, spec);
    if (spec.env && spec.env.length === 0)
        delete spec.env;
    if (spec.tty !== true)
        delete spec.tty;
    if (spec.ports && spec.ports.length === 0)
        delete spec.ports;
    if (spec.ports) {
        spec.ports = spec.ports.map((p) => p.protocol ? p : Object.assign({}, p, { protocol: "TCP" }));
    }
    return spec;
}
function containerSpecProps(props) {
    const { key, handle } = props, rest = tslib_1.__rest(props, ["key", "handle"]);
    return rest;
}
/** @internal */
function makePodManifest(props, volumes) {
    const { key, handle, isTemplate, metadata, config, children, volumes: origVolumes } = props, propsLL = tslib_1.__rest(props, ["key", "handle", "isTemplate", "metadata", "config", "children", "volumes"]);
    const containers = lodash_1.default.compact(core_1.childrenToArray(props.children)
        .map((c) => Container_1.isK8sContainerElement(c) ? c : null));
    const spec = Object.assign({}, propsLL, { containers: containers.map((c) => (Object.assign({}, containerSpecProps(c.props))))
            .map(defaultize)
            .map(utils_1.removeUndef), volumes });
    return {
        kind: "Pod",
        metadata: props.metadata,
        spec,
    };
}
exports.makePodManifest = makePodManifest;
function resolveMappedVolumeHandle(vol, deployID, toResolve) {
    for (const key in toResolve) {
        if (!Object.hasOwnProperty.call(toResolve, key))
            continue;
        if (vol[key] === undefined)
            continue;
        const field = toResolve[key].field;
        const h = vol[key][field];
        if (!core_1.isHandle(h))
            return vol;
        const target = h.target;
        if (!core_1.isMountedElement(target) && target !== null) {
            return Object.assign({}, vol, { [key]: Object.assign({}, vol[key], { [field]: `adapt-unresolved-${key}`, 
                    // Make sure we force k8s to wait for the handle to resolve
                    optional: false }) });
        }
        if (target === null) {
            return Object.assign({}, vol, { [key]: {
                    [field]: `adapt-null-${key}`,
                    // Make the volume optional so k8s skips it
                    optional: true,
                } });
        }
        if (!Resource_1.isResource(target)) {
            throw new Error(`Cannot have a non-resource handle target for a ${key} volume ${field}`);
        }
        const props = target.props;
        if (key.toLowerCase() !== props.kind.toLowerCase()) {
            throw new Error(`Cannot use handle to ${props.kind} as reference in ${key}`);
        }
        return Object.assign({}, vol, { [key]: Object.assign({}, vol[key], { [field]: manifest_support_1.resourceElementToName(target, deployID) }) });
    }
    return vol;
}
function resolveVolumeHandles(volumes, deployID) {
    if (volumes === undefined)
        return {};
    return {
        volumes: volumes.map((vol) => resolveMappedVolumeHandle(vol, deployID, { configMap: { field: "name" },
            secret: { field: "secretName" }
        }))
    };
}
/**
 * Component for Kubernetes Pods
 *
 * @public
 */
class Pod extends core_1.DeferredComponent {
    initialState() { return {}; }
    build(helpers) {
        this.setState(() => resolveVolumeHandles(this.props.volumes, helpers.deployID));
        const { key } = this.props;
        if (!key)
            throw new utils_1.InternalError("key is null");
        const children = core_1.childrenToArray(this.props.children);
        if (lodash_1.default.isEmpty(children))
            return null;
        if (!isContainerArray(children))
            return null;
        const containerNames = children.map((child) => child.props.name);
        const dupNames = dups(containerNames);
        if (!lodash_1.default.isEmpty(dupNames)) {
            throw new core_1.BuildNotImplemented(`Duplicate names within a pod: ${dupNames.join(", ")}`);
        }
        const manifest = makePodManifest(this.props, this.state.volumes);
        return (core_1.default.createElement(Resource_1.Resource, { key: key, 
            // tslint:disable-next-line: no-object-literal-type-assertion
            config: this.props.config, kind: "Pod", isTemplate: this.props.isTemplate, metadata: manifest.metadata, spec: manifest.spec }));
    }
    async status(_observe, buildData) {
        const succ = buildData.successor;
        if (!succ)
            return undefined;
        return succ.status();
    }
}
Pod.defaultProps = {
    isTemplate: false,
    metadata: {},
    dnsPolicy: "ClusterFirst",
    enableServiceLinks: true,
    hostIPC: false,
    hostPID: false,
    restartPolicy: "Always",
    securityContext: {},
    shareProcessNamespace: false,
    terminationGracePeriodSeconds: 30,
};
exports.Pod = Pod;
/**
 * Tests whether x is a Pod element
 *
 * @param x - value to test
 * @returns `true` if x is a Pod element, false otherwise
 *
 * @public
 */
function isPod(x) {
    if (!core_1.isElement(x))
        return false;
    if (x.componentType === Pod)
        return true;
    return false;
}
exports.isPod = isPod;
function isReady(status) {
    if (status.phase !== "Running")
        return false;
    const conditions = status.conditions;
    if (!conditions)
        return false;
    if (!util_1.isArray(conditions))
        return false;
    if (conditions.filter((c) => (c.type === "Ready") && (c.status === "True")).length !== 0)
        return true;
    return false;
}
function deployedWhen(statusObj) {
    const status = statusObj;
    if (!status || !status.status)
        return core_1.waiting(`Kubernetes cluster returned invalid status for Pod`);
    if (isReady(status.status))
        return true;
    let msg = `Pod state ${status.status.phase}`;
    if (Array.isArray(status.status.conditions)) {
        const failing = status.status.conditions
            .filter((cond) => cond.status !== "True")
            .map((cond) => cond.message)
            .join("; ");
        if (failing)
            msg += `: ${failing}`;
    }
    return core_1.waiting(msg);
}
/** @internal */
exports.podResourceInfo = {
    kind: "Pod",
    deployedWhen,
    statusQuery: async (props, observe, buildData) => {
        const obs = await observe(k8s_observer_1.K8sObserver, core_1.gql `
            query ($name: String!, $kubeconfig: JSON!, $namespace: String!) {
                withKubeconfig(kubeconfig: $kubeconfig) {
                    readCoreV1NamespacedPod(name: $name, namespace: $namespace) @all(depth: 100)
                }
            }`, {
            name: manifest_support_1.resourceIdToName(props.key, buildData.id, buildData.deployID),
            kubeconfig: props.config.kubeconfig,
            namespace: common_1.computeNamespaceFromMetadata(props.metadata)
        });
        return obs.withKubeconfig.readCoreV1NamespacedPod;
    },
};
manifest_support_1.registerResourceKind(exports.podResourceInfo);
//# sourceMappingURL=Pod.js.map