mirror of
https://github.com/zhigang1992/firebase-tools.git
synced 2026-01-12 22:47:24 +08:00
Ask for confirmation before deleting functions during 'firebase deploy' (#287)
This commit is contained in:
@@ -15,6 +15,11 @@ var utils = require("../lib/utils");
|
||||
|
||||
module.exports = new Command("functions:delete [filters...]")
|
||||
.description("delete one or more Cloud Functions by name or group name.")
|
||||
.option(
|
||||
"--region <region>",
|
||||
"Specify region of the function to be deleted. " +
|
||||
"If omitted, functions from all regions whose names match the filters will be deleted. "
|
||||
)
|
||||
.option("-f, --force", "No confirmation. Otherwise, a confirmation prompt will appear.")
|
||||
.before(requireAccess, [scopes.CLOUD_PLATFORM])
|
||||
.action(function(filters, options) {
|
||||
@@ -34,12 +39,14 @@ module.exports = new Command("functions:delete [filters...]")
|
||||
.listAll(projectId)
|
||||
.then(function(result) {
|
||||
var allFunctions = _.map(result, "name");
|
||||
return _.filter(allFunctions, function(functionName) {
|
||||
return _.some(
|
||||
return _.filter(allFunctions, function(name) {
|
||||
var regionMatches = options.region ? helper.getRegion(name) === options.region : true;
|
||||
var nameMatches = _.some(
|
||||
_.map(filterChunks, function(chunk) {
|
||||
return helper.functionMatchesGroup(functionName, chunk);
|
||||
return helper.functionMatchesGroup(name, chunk);
|
||||
})
|
||||
);
|
||||
return regionMatches && nameMatches;
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
|
||||
@@ -9,6 +9,7 @@ var logger = require("../../logger");
|
||||
var track = require("../../track");
|
||||
var utils = require("../../utils");
|
||||
var helper = require("../../functionsDeployHelper");
|
||||
var prompt = require("../../prompt");
|
||||
|
||||
var CLI_DEPLOYMENT_TOOL = "cli-firebase";
|
||||
var CLI_DEPLOYMENT_LABELS = {
|
||||
@@ -57,6 +58,55 @@ function _fetchTriggerUrls(projectId, ops, sourceUrl) {
|
||||
});
|
||||
}
|
||||
|
||||
var printSuccess = function(op) {
|
||||
_endTimer(op.func);
|
||||
utils.logSuccess(
|
||||
chalk.bold.green("functions[" + helper.getFunctionLabel(op.func) + "]: ") +
|
||||
"Successful " +
|
||||
op.type +
|
||||
" operation. "
|
||||
);
|
||||
if (op.triggerUrl && op.type !== "delete") {
|
||||
logger.info(
|
||||
chalk.bold("Function URL"),
|
||||
"(" + helper.getFunctionName(op.func) + "):",
|
||||
op.triggerUrl
|
||||
);
|
||||
}
|
||||
};
|
||||
var printFail = function(op) {
|
||||
_endTimer(op.func);
|
||||
failedDeployments += 1;
|
||||
utils.logWarning(
|
||||
chalk.bold.yellow("functions[" + helper.getFunctionLabel(op.func) + "]: ") + "Deployment error."
|
||||
);
|
||||
if (op.error.code === 8) {
|
||||
logger.debug(op.error.message);
|
||||
logger.info(
|
||||
"You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " +
|
||||
"and wait a few minutes before deploying again. Go to " +
|
||||
chalk.underline("https://firebase.google.com/docs/cli/#deploy_specific_functions") +
|
||||
" to learn more."
|
||||
);
|
||||
} else {
|
||||
logger.info(op.error.message);
|
||||
}
|
||||
};
|
||||
|
||||
var printTooManyOps = function() {
|
||||
utils.logWarning(
|
||||
chalk.bold.yellow("functions:") + " too many functions are being deployed, cannot poll status."
|
||||
);
|
||||
logger.info(
|
||||
"In a few minutes, you can check status at " +
|
||||
utils.consoleUrl(options.project, "/functions/logs")
|
||||
);
|
||||
logger.info(
|
||||
"You can use the --only flag to deploy only a portion of your functions in the future."
|
||||
);
|
||||
deployments = []; // prevents analytics tracking of deployments
|
||||
};
|
||||
|
||||
module.exports = function(context, options, payload) {
|
||||
if (!options.config.has("functions")) {
|
||||
return Promise.resolve();
|
||||
@@ -170,12 +220,12 @@ module.exports = function(context, options, payload) {
|
||||
throw new FirebaseError(
|
||||
"Function " +
|
||||
chalk.bold(functionName) +
|
||||
" was deployed using a legacy " +
|
||||
"trigger type and cannot be updated with the new SDK. To proceed with this deployment, you must first delete the " +
|
||||
"function by visiting the Cloud Console at: https://console.cloud.google.com/functions/list?project=" +
|
||||
projectId +
|
||||
"\n\nTo avoid service interruption, you may wish to create an identical function with a different name before " +
|
||||
"deleting this function.\n"
|
||||
" was deployed using a legacy trigger type and cannot be updated without deleting " +
|
||||
"the previous function. Follow the instructions on " +
|
||||
chalk.underline(
|
||||
"https://firebase.google.com/docs/functions/manage-functions#modify-trigger"
|
||||
) +
|
||||
" for how to change the trigger without losing events.\n"
|
||||
);
|
||||
} else {
|
||||
_startTimer(name, "update");
|
||||
@@ -200,7 +250,7 @@ module.exports = function(context, options, payload) {
|
||||
.value();
|
||||
|
||||
// Delete functions
|
||||
_.chain(existingFunctions)
|
||||
var functionsToDelete = _.chain(existingFunctions)
|
||||
.filter(function(functionInfo) {
|
||||
if (typeof functionInfo.labels === "undefined") {
|
||||
return (
|
||||
@@ -213,30 +263,85 @@ module.exports = function(context, options, payload) {
|
||||
.map(pluckName)
|
||||
.difference(uploadedNames)
|
||||
.intersection(deleteReleaseNames)
|
||||
.map(function(name) {
|
||||
var functionName = helper.getFunctionName(name);
|
||||
var region = helper.getRegion(name);
|
||||
|
||||
utils.logBullet(
|
||||
chalk.bold.cyan("functions: ") +
|
||||
"deleting function " +
|
||||
chalk.bold(helper.getFunctionLabel(name)) +
|
||||
"..."
|
||||
);
|
||||
_startTimer(name, "delete");
|
||||
deployments.push({
|
||||
name: name,
|
||||
retryFunction: function() {
|
||||
return gcp.cloudfunctions.delete({
|
||||
projectId: projectId,
|
||||
region: region,
|
||||
functionName: functionName,
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.value();
|
||||
|
||||
if (functionsToDelete.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
var deleteList = _.map(functionsToDelete, function(func) {
|
||||
return "\t" + helper.getFunctionLabel(func);
|
||||
}).join("\n");
|
||||
|
||||
if (options.nonInteractive) {
|
||||
var deleteCommands = _.map(functionsToDelete, function(func) {
|
||||
return (
|
||||
"\tfirebase functions:delete " +
|
||||
helper.getFunctionName(func) +
|
||||
" --region " +
|
||||
helper.getRegion(func)
|
||||
);
|
||||
}).join("\n");
|
||||
|
||||
throw new FirebaseError(
|
||||
"The following functions are found in your project but do not exist in your local source code:\n" +
|
||||
deleteList +
|
||||
"\n\nAborting because deletion cannot proceed in non-interactive mode. To fix, manually delete the functions by running:\n" +
|
||||
chalk.bold(deleteCommands)
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"\nThe following functions are found in your project but do not exist in your local source code:\n" +
|
||||
deleteList +
|
||||
"\n\nIf you are renaming a function or changing its region, it is recommended that you create the new " +
|
||||
"function first before deleting the old one to prevent event loss. For more info, visit " +
|
||||
chalk.underline(
|
||||
"https://firebase.google.com/docs/functions/manage-functions#modify" + "\n"
|
||||
)
|
||||
);
|
||||
|
||||
return prompt
|
||||
.once({
|
||||
type: "confirm",
|
||||
name: "confirm",
|
||||
default: false,
|
||||
message:
|
||||
"Would you like to proceed with deletion? Selecting no will continue the rest of the deployments.",
|
||||
})
|
||||
.then(function(proceed) {
|
||||
if (!proceed) {
|
||||
if (deployments.length !== 0) {
|
||||
utils.logBullet(
|
||||
chalk.bold.cyan("functions: ") + "continuing with other deployments."
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
functionsToDelete.forEach(function(name) {
|
||||
var functionName = helper.getFunctionName(name);
|
||||
var region = helper.getRegion(name);
|
||||
|
||||
utils.logBullet(
|
||||
chalk.bold.cyan("functions: ") +
|
||||
"deleting function " +
|
||||
chalk.bold(helper.getFunctionLabel(name)) +
|
||||
"..."
|
||||
);
|
||||
_startTimer(name, "delete");
|
||||
deployments.push({
|
||||
name: name,
|
||||
retryFunction: function() {
|
||||
return gcp.cloudfunctions.delete({
|
||||
projectId: projectId,
|
||||
region: region,
|
||||
functionName: functionName,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function() {
|
||||
return utils.promiseAllSettled(
|
||||
_.map(deployments, function(op) {
|
||||
return op.retryFunction().then(function(res) {
|
||||
@@ -256,55 +361,6 @@ module.exports = function(context, options, payload) {
|
||||
.value();
|
||||
failedDeployments += failedCalls.length;
|
||||
|
||||
var printSuccess = function(op) {
|
||||
_endTimer(op.func);
|
||||
utils.logSuccess(
|
||||
chalk.bold.green("functions[" + helper.getFunctionLabel(op.func) + "]: ") +
|
||||
"Successful " +
|
||||
op.type +
|
||||
" operation. "
|
||||
);
|
||||
if (op.triggerUrl && op.type !== "delete") {
|
||||
logger.info(
|
||||
chalk.bold("Function URL"),
|
||||
"(" + helper.getFunctionName(op.func) + "):",
|
||||
op.triggerUrl
|
||||
);
|
||||
}
|
||||
};
|
||||
var printFail = function(op) {
|
||||
_endTimer(op.func);
|
||||
failedDeployments += 1;
|
||||
utils.logWarning(
|
||||
chalk.bold.yellow("functions[" + helper.getFunctionLabel(op.func) + "]: ") +
|
||||
"Deployment error."
|
||||
);
|
||||
if (op.error.code === 8) {
|
||||
logger.debug(op.error.message);
|
||||
logger.info(
|
||||
"You have exceeded your deployment quota, please deploy your functions in batches by using the --only flag, " +
|
||||
"and wait a few minutes before deploying again. Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more."
|
||||
);
|
||||
} else {
|
||||
logger.info(op.error.message);
|
||||
}
|
||||
};
|
||||
|
||||
var printTooManyOps = function() {
|
||||
utils.logWarning(
|
||||
chalk.bold.yellow("functions:") +
|
||||
" too many functions are being deployed, cannot poll status."
|
||||
);
|
||||
logger.info(
|
||||
"In a few minutes, you can check status at " +
|
||||
utils.consoleUrl(options.project, "/functions/logs")
|
||||
);
|
||||
logger.info(
|
||||
"You can use the --only flag to deploy only a portion of your functions in the future."
|
||||
);
|
||||
deployments = []; // prevents analytics tracking of deployments
|
||||
};
|
||||
|
||||
return _fetchTriggerUrls(projectId, successfulCalls, sourceUrl)
|
||||
.then(function() {
|
||||
return helper.pollDeploys(successfulCalls, printSuccess, printFail, printTooManyOps);
|
||||
|
||||
Reference in New Issue
Block a user