blob: 293ed5032fd317629d3f3c361c69c060c508dcc8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
import fs from 'fs/promises';
import { expandEnv } from './env_utils';
const { parse } = await import('shell-quote');
const MAINS = ["__main__.py", "index.js", "index.php", "main.go"];
const queue = [];
const activeDeployments = new Map();
let dryRun = false;
export function setDryRun(b) {
dryRun = b;
}
async function exec(cmd) {
console.log("$", cmd);
cmd = expandEnv(cmd);
const cmdArgs = parse(cmd).filter(arg => typeof arg === "string");
const proc = Bun.spawn(cmdArgs, {
shell: true,
env: process.env,
cwd: process.env.OPS_PWD,
stdio: ['inherit', 'inherit', 'inherit']
});
await proc.exited;
}
async function extractArgs(files) {
const res = [];
for (const file of files) {
if (await fs.exists(file)) {
const fileContent = await fs.readFile(file, "utf-8");
const lines = fileContent.split("\n");
for (const line of lines) {
if (line.startsWith("#-")) {
res.push(line.trim().substring(1));
}
if (line.startsWith("//-")) {
res.push(line.trim().substring(2));
}
}
}
}
return res;
}
const packageDone = new Set();
export async function deployPackage(pkg) {
const ppath = `packages/${pkg}.args`;
const pargs = await extractArgs([ppath]);
const args = pargs.join(" ");
const cmd = `ops package update ${pkg} ${args}`;
if (!packageDone.has(cmd)) {
await exec(cmd);
packageDone.add(cmd);
}
}
export async function buildZip(pkg, action) {
await exec(`ops ide util zip A=${pkg}/${action}`);
return `packages/${pkg}/${action}.zip`;
}
export async function buildAction(pkg, action) {
await exec(`ops ide util action A=${pkg}/${action}`);
return `packages/${pkg}/${action}.zip`;
}
export async function deployAction(artifact) {
let pkg = '', name='', typ = '';
if (activeDeployments.has(artifact)) {
queue.push(artifact);
return;
}
activeDeployments.set(artifact, true);
const indexInQueue = queue.indexOf(artifact);
if (indexInQueue>-1) {
console.log(`⚙️ Deploying ${artifact} (from queue: ${indexInQueue})`);
}
try {
const sp = artifact.split("/");
const spData = sp[sp.length - 1].split(".");
name = spData[0];
typ = spData[1];
pkg = sp[1];
} catch {
console.log("❌ cannot deploy", artifact);
return;
}
await deployPackage(pkg);
let toInspect;
if (typ === "zip") {
const base = artifact.slice(0, -4);
toInspect = MAINS.map((m) => `${base}/${m}`);
} else {
toInspect = [artifact];
}
const args = (await extractArgs(toInspect)).join(" ");
const actionName = `${pkg}/${name}`;
await exec(`ops action update ${actionName} ${artifact} ${args}`);
activeDeployments.delete(artifact);
if (queue.length > 0) {
const nextArtifact = queue.shift();
console.debug(`📦 deploying from queue artifact ${nextArtifact}`);
await deploy(nextArtifact);
}
}
/**
* Deploy a `file`
* @param file
*/
export async function deploy(file) {
// Uncomment the lines below to test specific files
// const file = "packages/deploy/hello.py";
// const file = "packages/deploy/multi.zip";
// const file = "packages/deploy/multi/__main__.py";
// const file = "packages/deploy/multi/requirements.txt";
const stat = await fs.stat(file)
if (stat.isDirectory()) {
for (const start of MAINS) {
const sub = `${file}/${start}`;
if (await fs.exists(sub)) {
file = sub;
break;
}
}
}
const sp = file.split("/");
if (sp.length > 3) {
buildZip(sp[1], sp[2]);
file = await buildAction(sp[1], sp[2]);
}
console.log(`Deploying ${file}`);
await deployAction(file);
}
/**
* Deploy a `manifest.yaml` file using `ops -wsk project`
* @param artifact
*/
export async function deployProject(artifact) {
if (await fs.exists(artifact)) {
const manifestContent = await Bun.file(artifact).text();
if (manifestContent.indexOf('packages:')!==-1) {
await exec(`ops -wsk project deploy --manifest ${artifact}`);
} else {
console.log(`⚠️ Warning: it seems that the ${artifact} file is not a valid manifest file. Skipping`);
}
}
}