Skip to content

Implement project specific undeploy command #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion ide/deploy/index.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import {program} from 'commander';
import {scan} from './scan.js';
import {watchAndDeploy, globalWatcher} from './watch.js';
import {setDryRun, deploy} from './deploy.js';
import {undeploy, setDryRun as setUndeployDryRun} from './undeploy.js';
import {build} from './client.js';


@@ -74,15 +75,47 @@ async function main() {
.option('-d, --deploy', 'Deploy')
.option('-w, --watch', 'Watch for changes')
.option('-s, --single <string>', 'Deploy a single action, either a single file or a directory.', '')
.option('-u, --undeploy', 'Undeploy actions and packages from the current project')
.parse(process.argv);

const options = program.opts();
const directory = program.args[0];

setDryRun(options.dryRun);
setUndeployDryRun(options.dryRun);
process.chdir(directory);

if (options.watch) {
if (options.undeploy) {
// Undeploy actions and packages from the current project
let success;

if (options.single !== '') {
// If a single action is specified, undeploy just that action
let action = options.single;
if (!action.startsWith('packages/')) {
action = `packages/${action}`;
}

// Extract the package and action name
const parts = action.split('/');
if (parts.length >= 3) {
const pkg = parts[1];
const actionName = parts[2].split('.')[0]; // Remove file extension if present
success = undeploy(`${pkg}/${actionName}`);
} else {
// If the format is already package/action
success = undeploy(action);
}
} else {
// Otherwise, undeploy all actions and packages from the current project
success = undeploy();
}

if (!success) {
process.exit(1);
}
process.exit(0);
} else if (options.watch) {
checkPort();
if (!options.fast) {
await scan();
17 changes: 17 additions & 0 deletions ide/deploy/info.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
// 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 { getOpenServerlessConfig} from './client.js';
import {program} from "commander";
import process from "process";
12 changes: 10 additions & 2 deletions ide/deploy/scan.js
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import {glob} from 'glob';
import {buildAction, buildZip, deployAction, deployPackage, deployProject} from './deploy.js';
import {getOpenServerlessConfig} from './client.js';
import {config} from "dotenv";
import {syncDeployInfo} from "./syncDeployInfo";

/**
* This function will prepare and deploy the functions in `packages` directory.
@@ -158,13 +159,20 @@ export async function scan() {
manifests.sort((a, b) => a.localeCompare(b));
}
if (manifests.length >0 ) {
if (Bun.file('packeges/.env')) {
if (Bun.file('packages/.env')) {
console.log("Found packages .env file. Reading it");
config({ path: "./package/.env" });
config({ path: "./packages/.env" });
}
for (const manifest of manifests) {
console.log(">>> Manifest:", manifest);
deployProject(manifest);
}
}

try {
// Save deployment information with the new structure
syncDeployInfo(packages, deployments);
} catch (error) {
console.error("Error saving deployment information:", error);
}
}
124 changes: 124 additions & 0 deletions ide/deploy/syncDeployInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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 {existsSync, mkdirSync, writeFileSync, readFileSync} from "fs";

/**
* Synchronizes deployment information by saving the provided package and deployment data
* to a persisted `.ops/deployment.json` file. If the directory `.ops` does not exist, it
* is created before saving the information.
*
* The deployment information is organized by packages, with each package containing an array
* of its actions. This allows for more granular control when undeploying specific actions.
*
* @param {Set<string>} packages - A set of package names to include in the deployment data.
* @param {Set<string>} deployments - A set of deployment identifiers to include in the deployment data.
* @return {void}
*/
export function syncDeployInfo(packages, deployments) {
if (!existsSync('.ops')) {
mkdirSync('.ops', { recursive: true });
}

// Create a structured object with packages as keys and arrays of actions as values
const packageActions = {};

// Initialize packages with empty arrays
for (const pkg of packages) {
packageActions[pkg] = [];
}

// Add actions to their respective packages
for (const deployment of deployments) {
try {
const sp = deployment.split("/");
const spData = sp[sp.length - 1].split(".");
const name = spData[0];
const pkg = sp[1];

// If the package exists in our structure, add the action to it
if (packageActions[pkg]) {
packageActions[pkg].push(name);
}
} catch (error) {
console.error(`Error parsing deployment path ${deployment}:`, error);
}
}

const deploymentInfo = {
packages: Array.from(packages),
packageActions: packageActions
};

writeFileSync('.ops/deployment.json', JSON.stringify(deploymentInfo, null, 2));
console.log("> Saved deployment information to .ops/deployment.json");
}

/**
* Removes a specific action from the deployment information.
*
* @param {string} actionName - The name of the action to remove in the format "package/action".
* @return {boolean} - True if the action was found and removed, false otherwise.
*/
export function removeActionFromDeployInfo(actionName) {
if (!existsSync('.ops/deployment.json')) {
console.error('Error: No deployment information found.');
return false;
}

try {
const deploymentInfo = JSON.parse(readFileSync('.ops/deployment.json', 'utf8'));
const [pkg, action] = actionName.split('/');

if (!deploymentInfo.packageActions || !deploymentInfo.packageActions[pkg]) {
console.error(`❌ Error: Package ${pkg} not found in deployment information.`);
return false;
}

const actionIndex = deploymentInfo.packageActions[pkg].indexOf(action);
if (actionIndex === -1) {
console.error(`❌ Error: Action ${action} not found in package ${pkg}.`);
return false;
}

// Remove the action from the package
deploymentInfo.packageActions[pkg].splice(actionIndex, 1);

// If the package has no more actions, remove it from the packages list
if (deploymentInfo.packageActions[pkg].length === 0) {
const packageIndex = deploymentInfo.packages.indexOf(pkg);
if (packageIndex !== -1) {
deploymentInfo.packages.splice(packageIndex, 1);
}
delete deploymentInfo.packageActions[pkg];
}

writeFileSync('.ops/deployment.json', JSON.stringify(deploymentInfo, null, 2));
console.log(`> Removed ${actionName} from deployment information.`);
return true;
} catch (error) {
console.error("❌ Error updating deployment information:", error);
return false;
}
}

/**
* Cleans up deployment information by resetting and synchronizing deployment data.
*/
export function cleanupDeployInfo() {
syncDeployInfo(new Set(), new Set());
}
115 changes: 115 additions & 0 deletions ide/deploy/undeploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 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 { existsSync, readFileSync } from 'fs';
import { spawnSync } from 'child_process';
import { cleanupDeployInfo, removeActionFromDeployInfo } from "./syncDeployInfo";

let dryRun = false;

export function setDryRun(b) {
dryRun = b;
}

function exec(cmd) {
console.log("$", cmd);
if (!dryRun) {
spawnSync(cmd, { shell: true, env: process.env, stdio: "inherit" });
}
}

/**
* Undeploy a specific action and update the deployment information
* @param {string} actionName - The name of the action to undeploy in the format "package/action"
* @returns {boolean} true if successful, false if error
*/
export function undeployAction(actionName) {
console.log(`> Undeploying action: ${actionName}`);

try {
// Execute the undeploy command
exec(`$OPS action delete ${actionName}`);

// Update the deployment information
const success = removeActionFromDeployInfo(actionName);
if (success) {
console.log(`> Action ${actionName} successfully undeployed and removed from deployment information.`);
return true;
} else {
console.error(`> Action ${actionName} was undeployed but could not be removed from deployment information.`);
return false;
}
} catch (error) {
console.error(`Error undeploying action ${actionName}:`, error);
return false;
}
}

/**
* Undeploy actions and packages based on the deployment information in .ops/deployment.json
* If no deployment information is found, return an error
* @param {string} [specificAction] - Optional specific action to undeploy
* @returns {boolean} true if successful, false if error
*/
export function undeploy(specificAction) {
// If a specific action is provided, undeploy just that action
if (specificAction) {
return undeployAction(specificAction);
}

// Otherwise, undeploy all actions and packages from the current project
if (!existsSync('.ops/deployment.json')) {
console.error('❌ Error: No OpenServerless project found in the current directory.');
console.error('❌ Please run this command in a directory with an OpenServerless project.');
return false;
}

try {
const deploymentInfo = JSON.parse(readFileSync('.ops/deployment.json', 'utf8'));
const { packages, packageActions } = deploymentInfo;

if (!packages || !packageActions || packages.length === 0) {
console.error('❌ Error: No deployment information found.');
return false;
}

console.log("> Undeploy actions and packages from the current project:");

// Undeploy actions
for (const pkg of packages) {
const actions = packageActions[pkg] || [];
for (const action of actions) {
const actionName = `${pkg}/${action}`;
console.log(`>> Undeploy action: ${actionName}`);
exec(`$OPS action delete ${actionName}`);
}
}

// Undeploy packages
for (const pkg of packages) {
console.log(`>> Undeploy package: ${pkg}`);
exec(`$OPS package delete ${pkg}`);
}

cleanupDeployInfo();
console.log("> Undeployment completed successfully.");
return true;
} catch (error) {
console.error("❌ Error undeploy:", error);
return false;
}
}
4 changes: 2 additions & 2 deletions ide/docopts.md
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ Usage:
ide login login in openserverless
ide devel activate development mode
ide deploy deploy everything or just one action
ide undeploy undeploy everything or just one action
ide undeploy undeploy actions and packages from the current project or just one action
ide clean clean the temporay files
ide setup setup the ide
ide serve serve web area
@@ -60,4 +60,4 @@ Usage:
```
--fast Skip the initial deployment step and go in incremental update mode
--dry-run Simulates the execution without making any actual changes
```
```
Loading
Oops, something went wrong.