UNPKG

@adpt/cloud

Version:
228 lines 8.86 kB
"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 }); 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_; } } CloudRun.defaultProps = { trafficPct: 100, memory: "128M", cpu: 1, allowUnauthenticated: false }; exports.CloudRun = CloudRun; 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({}, 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