"use strict";
/*
 * Copyright 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 });
exports.CloudRunAdapter = exports.makeCloudRunName = exports.CloudRun = void 0;
const tslib_1 = require("tslib");
const core_1 = tslib_1.__importStar(require("@adpt/core"));
const utils_1 = require("@adpt/utils");
const ld = tslib_1.__importStar(require("lodash"));
const action_1 = require("../action");
const common_1 = require("../common");
const Container_1 = require("../Container");
const docker_1 = require("../docker");
const env_1 = require("../env");
const commands_1 = require("./commands");
/**
 * Decides if an existing Run deployment is scheduled for deletion
 */
function isDeleting(info) {
    return (info !== undefined) && ("deletionTimestamp" in info.metadata);
}
/**
 * Primitive Component for GCP Cloud Run deployments
 * @public
 */
class CloudRun extends action_1.Action {
    constructor(props) {
        super(props);
        this.deployedWhen = async (goalStatus) => {
            const statObj = await commands_1.cloudRunDescribe(this.config(this.deployInfo.deployID));
            if (goalStatus === core_1.DeployStatus.Destroyed) {
                if (statObj === undefined)
                    return true;
                return core_1.waiting(`Waiting for CloudRun deployment to be destroyed`);
            }
            if (statObj == null) {
                return core_1.waiting(`Waiting for CloudRun deployment to be created`);
            }
            return isReady(statObj);
        };
    }
    validate() {
        const children = core_1.childrenToArray(this.props.children);
        if (!ld.isEmpty(children))
            return "Resource elements cannot have children";
        if ((this.props.port) < 1 || (this.props.port > 65535)) {
            throw new Error(`Invalid port ${this.props.port} (must be between 1 and 65535)`);
        }
        if ((this.props.trafficPct <= 0) || (this.props.trafficPct > 100)) {
            throw new Error(`Invalid trafficPct ${this.props.trafficPct} (must be an integer between 1 and 100)`);
        }
        //Do other validations of config here
        return;
    }
    async shouldAct(op, ctx) {
        const deployID = ctx.buildData.deployID;
        const config = this.config(deployID);
        const name = config.name;
        const oldManifest = await commands_1.cloudRunDescribe(config);
        switch (op) {
            case core_1.ChangeType.create:
            case core_1.ChangeType.modify:
            case core_1.ChangeType.replace:
                // See Issue #220
                if (oldManifest === undefined || isDeleting(oldManifest)) {
                    return {
                        act: true,
                        detail: `Creating CloudRun deployment ${name}`
                    };
                }
                else {
                    return { act: true, detail: `Updating CloudRun deployment ${name}` };
                }
            case core_1.ChangeType.delete:
                if (oldManifest && !isDeleting(oldManifest)) {
                    return {
                        act: true,
                        detail: `Deleting CloudRun deployment ${name}`
                    };
                }
                return false;
            case core_1.ChangeType.none:
                return false;
        }
    }
    async action(op, ctx) {
        const deployID = ctx.buildData.deployID;
        const config = this.config(deployID);
        const info = await commands_1.cloudRunDescribe(config);
        let deleted = false;
        if (isDeleting(info)) {
            //Wait for deleting to complete, else create/modify/apply will fail
            await commands_1.cloudRunDelete(config);
            deleted = true;
        }
        switch (op) {
            case core_1.ChangeType.create:
            case core_1.ChangeType.modify:
            case core_1.ChangeType.replace:
                //See Issue #220
                await commands_1.cloudRunDeploy(config);
                await commands_1.cloudRunUpdateTraffic(config);
                return;
            case core_1.ChangeType.delete:
                if (deleted)
                    return;
                await commands_1.cloudRunDelete(config);
                return;
            case core_1.ChangeType.none:
                return;
        }
    }
    mountedElement() {
        const hand = this.props.handle;
        if (hand === undefined)
            throw new utils_1.InternalError("element requested but props.handle undefined");
        const elem = hand.mountedOrig;
        if (elem == null)
            throw new utils_1.InternalError(`element requested but handle.mountedOrig is ${elem}`);
        return elem;
    }
    config(deployID) {
        if (this.config_)
            return this.config_;
        const elem = this.mountedElement();
        const key = this.props.key;
        if (key == null)
            throw new Error("Internal Error: key is falsey");
        this.config_ = {
            name: this.props.serviceName || exports.makeCloudRunName(key, elem.id, deployID),
            env: env_1.mergeEnvSimple(this.props.env) || {},
            args: this.props.args || [],
            image: this.props.image,
            port: this.props.port,
            region: this.props.region,
            cpu: this.props.cpu,
            memory: this.props.memory,
            trafficPct: this.props.trafficPct,
            allowUnauthenticated: this.props.allowUnauthenticated,
            globalOpts: {
                configuration: this.props.configuration
            }
        };
        return this.config_;
    }
}
exports.CloudRun = CloudRun;
CloudRun.defaultProps = {
    trafficPct: 100,
    memory: "128M",
    cpu: 1,
    allowUnauthenticated: false
};
function isReady(status) {
    if (!status || !status.status)
        return core_1.waiting(`CloudRun cluster returned invalid status for Pod`);
    if (status.status.phase === "Running")
        return true;
    if (status.status.conditions == null)
        return core_1.waiting("Waiting for CloudRun conditions");
    if (!Array.isArray(status.status.conditions))
        return core_1.waiting("Waiting for CloudRun to populate conditions");
    const conditions = status.status.conditions;
    // For Knative, which CloudRun is based on,
    // if there is a condition with type Ready and it is True,
    // all conditions are satisfied for readiness.
    //
    // Note(manishv)  It is unclear to me if this means all other conditions
    // must also be true, or that there will be no new conditions added later
    // but this check seems to suffice.
    const ready = conditions
        .find((cond) => (cond.status === "True" && cond.type === "Ready"));
    if (ready !== undefined)
        return true;
    // If there is no Ready condition type, or it is not True,
    // The status of all other false conditions gives us the best information
    // on why the service is not ready yet.
    let msg = "CloudRun not ready";
    const notReady = conditions
        .filter((cond) => cond.status !== "True")
        .map((cond) => cond.message)
        .join("; ");
    if (notReady)
        msg += `: ${notReady}`;
    return core_1.waiting(msg);
}
/**
 * Exported for testing only
 *
 * @internal
 */
exports.makeCloudRunName = common_1.makeResourceName(/[^a-z-]/g, 63);
/**
 * Temporary adapter to allow handle for image
 *
 * @beta
 */
function CloudRunAdapter(propsIn) {
    const props = propsIn;
    const { handle: origHandle } = props, propsNoHandle = tslib_1.__rest(props, ["handle"]);
    const cloudRun = core_1.handle();
    const regImage = core_1.handle();
    const helpers = core_1.useBuildHelpers();
    const image = Container_1.useLatestImageFrom(regImage);
    let crElem = null;
    if (image) {
        const crProps = Object.assign(Object.assign({}, propsNoHandle), { image });
        crElem = core_1.default.createElement(CloudRun, Object.assign({ handle: cloudRun }, crProps));
        origHandle.replaceTarget(crElem, helpers);
    }
    return core_1.default.createElement(core_1.Sequence, null,
        core_1.default.createElement(docker_1.RegistryDockerImage, { handle: regImage, imageSrc: props.image, registryUrl: props.registryUrl }),
        crElem);
}
exports.CloudRunAdapter = CloudRunAdapter;
//# sourceMappingURL=CloudRun.js.map