@adpt/cloud
Version:
AdaptJS cloud component library
228 lines • 8.86 kB
JavaScript
;
/*
* 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