Skip to content

Commit

Permalink
Functions List Command (firebase#3727)
Browse files Browse the repository at this point in the history
* PoC initial api calls

* moving list functionality into helper method and added unit tests

* update changelog

* fix lint

* adding jsdoc comment

* Remove HttpsTrigger.allowInsecure (firebase#3733)

* Remove HttpsTrigger.allowInsecure

* Formatter

* Create new endpoint type definition (firebase#3731)

* Create new endpoint type definition

* Fix build breaks

* Remove HttpsTrigger.allowInsecure

* Formatter

* Add type discrimiation functions and fix break

* Fix bug where CLI would crash when customers upload an empty functions project (firebase#3705)

* Fix bug where CLI would crash when customers upload an empty functions project

* Changelog

* Add support for --non-interactive to ext:install, and support updating existing instances (firebase#3710)

* started adding noninteractive support to ext:install

* Refactoring ext-install to support non-interactive runs and offer updates to existing instances

* self review

* pr fixes

* update changelog

* moving list function to backend and table creation to command, added unit tests for comparing functions

* adding listFunctions.ts back and move compare functions to backend

* Update CHANGELOG.md

Co-authored-by: Bryan Kendall <bkend@google.com>

* fixing pr comments

Co-authored-by: Thomas Bouldin <inlined@users.noreply.github.com>
Co-authored-by: joehan <joehanley@google.com>
Co-authored-by: Bryan Kendall <bkend@google.com>
  • Loading branch information
4 people authored and devpeerapong committed Dec 14, 2021
1 parent cc6b860 commit fa450d0
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- `ext:install` now supports `--force` and `--non-interactive` flags.
- Fixes a crash when customers deploy an empty Cloud Functions project (#3705)
- Fixes (and implements) `--no-authorized-domains`, skipping syncing with Firebase Auth, when deploying to a Firebase Hosting channel (#3740).
- Adds a command (`functions:list`) for listing all functions in the Firebase project.
54 changes: 54 additions & 0 deletions src/commands/functions-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Command } from "../command";
import { FirebaseError } from "../error";
import * as args from "../deploy/functions/args";
import { needProjectId } from "../projectUtils";
import { Options } from "../options";
import { requirePermissions } from "../requirePermissions";
import * as backend from "../deploy/functions/backend";
import { listFunctions } from "../functions/listFunctions";
import { previews } from "../previews";
import { logger } from "../logger";
import Table = require("cli-table");

export default new Command("functions:list")
.description("list all deployed functions in your Firebase project")
.before(requirePermissions, ["cloudfunctions.functions.list"])
.action(async (options: Options) => {
try {
const context = {
projectId: needProjectId(options),
} as args.Context;
const functionList = await listFunctions(context);
const table = previews.functionsv2
? new Table({
head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
style: { head: ["yellow"] },
})
: new Table({
head: ["Function", "Trigger", "Location", "Memory", "Runtime"],
style: { head: ["yellow"] },
});
for (const fnSpec of functionList.functions) {
const trigger = backend.isEventTrigger(fnSpec.trigger) ? fnSpec.trigger.eventType : "https";
const availableMemoryMb = fnSpec.availableMemoryMb || "---";
const entry = previews.functionsv2
? [
fnSpec.entryPoint,
fnSpec.platform === "gcfv2" ? "v2" : "v1",
trigger,
fnSpec.region,
availableMemoryMb,
fnSpec.runtime,
]
: [fnSpec.entryPoint, trigger, fnSpec.region, availableMemoryMb, fnSpec.runtime];
table.push(entry);
}
logger.info(table.toString());
return functionList;
} catch (err) {
throw new FirebaseError(`Failed to list functions ${err.message}`, {
exit: 1,
original: err,
});
}
});
1 change: 1 addition & 0 deletions src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ module.exports = function (client) {
client.functions.delete = loadCommand("functions-delete");
client.functions.log = loadCommand("functions-log");
client.functions.shell = loadCommand("functions-shell");
client.functions.list = loadCommand("functions-list");
if (previews.deletegcfartifacts) {
client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
}
Expand Down
22 changes: 22 additions & 0 deletions src/deploy/functions/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,3 +536,25 @@ export async function checkAvailability(context: Context, want: Backend): Promis
);
}
}

// To be a bit more deterministic, print function lists in a prescribed order.
// Future versions might want to compare regions by GCF/Run pricing tier before
// location.
export function compareFunctions(left: FunctionSpec, right: FunctionSpec): number {
if (left.platform != right.platform) {
return right.platform < left.platform ? -1 : 1;
}
if (left.region < right.region) {
return -1;
}
if (left.region > right.region) {
return 1;
}
if (left.id < right.id) {
return -1;
}
if (left.id > right.id) {
return 1;
}
return 0;
}
27 changes: 3 additions & 24 deletions src/deploy/functions/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,6 @@ import * as pricing from "./pricing";
import * as utils from "../../utils";
import { Options } from "../../options";

// To be a bit more deterministic, print function lists in a prescribed order.
// Future versions might want to compare regions by GCF/Run pricing tier before
// location.
function compareFunctions(left: backend.FunctionSpec, right: backend.FunctionSpec): number {
if (left.platform != right.platform) {
return right.platform < left.platform ? -1 : 1;
}
if (left.region < right.region) {
return -1;
}
if (left.region > right.region) {
return 1;
}
if (left.id < right.id) {
return -1;
}
if (left.id > right.id) {
return 1;
}
return 0;
}
/**
* Checks if a deployment will create any functions with a failure policy
* or add a failure policy to an existing function.
Expand Down Expand Up @@ -66,7 +45,7 @@ export async function promptForFailurePolicies(

const warnMessage =
"The following functions will newly be retried in case of failure: " +
clc.bold(newRetryFunctions.sort(compareFunctions).map(getFunctionLabel).join(", ")) +
clc.bold(newRetryFunctions.sort(backend.compareFunctions).map(getFunctionLabel).join(", ")) +
". " +
"Retried executions are billed as any other execution, and functions are retried repeatedly until they either successfully execute or the maximum retry period has elapsed, which can be up to 7 days. " +
"For safety, you might want to ensure that your functions are idempotent; see https://firebase.google.com/docs/functions/retries to learn more.";
Expand Down Expand Up @@ -108,7 +87,7 @@ export async function promptForFunctionDeletion(
return true;
}
const deleteList = functionsToDelete
.sort(compareFunctions)
.sort(backend.compareFunctions)
.map((fn) => "\t" + getFunctionLabel(fn))
.join("\n");

Expand Down Expand Up @@ -197,7 +176,7 @@ export async function promptForMinInstances(
// Add Tier 1 or Tier 2 annotations to functionLines
const functionLines = want
.filter((fn) => fn.minInstances)
.sort(compareFunctions)
.sort(backend.compareFunctions)
.map((fn) => {
return (
`\t${getFunctionLabel(fn)}: ${fn.minInstances} instances, ` +
Expand Down
16 changes: 16 additions & 0 deletions src/functions/listFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as backend from "../deploy/functions/backend";
import { previews } from "../previews";
import { Context } from "../deploy/functions/args";

/**
* Lists all functions of the Firebase project in order
* @param context the Context of the project
* @returns a mapping that contains an array of {@link FunctionSpec} in order under the 'functions' key
*/
export async function listFunctions(
context: Context
): Promise<{ functions: backend.FunctionSpec[] }> {
const functionSpecs = (await backend.existingBackend(context, true)).cloudFunctions;
functionSpecs.sort(backend.compareFunctions);
return { functions: functionSpecs };
}
87 changes: 87 additions & 0 deletions src/test/deploy/functions/backend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,91 @@ describe("Backend", () => {
});
});
});

describe("compareFunctions", () => {
const fnMembers = {
project: "project",
runtime: "nodejs14",
trigger: {},
};

it("should compare different platforms", () => {
const left: backend.FunctionSpec = {
id: "v1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "v1",
...fnMembers,
};
const right: backend.FunctionSpec = {
id: "v2",
region: "us-west1",
platform: "gcfv2",
entryPoint: "v2",
...fnMembers,
};

expect(backend.compareFunctions(left, right)).to.eq(1);
expect(backend.compareFunctions(right, left)).to.eq(-1);
});

it("should compare different regions, same platform", () => {
const left: backend.FunctionSpec = {
id: "v1",
region: "us-west1",
platform: "gcfv1",
entryPoint: "v1",
...fnMembers,
};
const right: backend.FunctionSpec = {
id: "newV1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "newV1",
...fnMembers,
};

expect(backend.compareFunctions(left, right)).to.eq(1);
expect(backend.compareFunctions(right, left)).to.eq(-1);
});

it("should compare different ids, same platform & region", () => {
const left: backend.FunctionSpec = {
id: "v1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "v1",
...fnMembers,
};
const right: backend.FunctionSpec = {
id: "newV1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "newV1",
...fnMembers,
};

expect(backend.compareFunctions(left, right)).to.eq(1);
expect(backend.compareFunctions(right, left)).to.eq(-1);
});

it("should compare same ids", () => {
const left: backend.FunctionSpec = {
id: "v1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "v1",
...fnMembers,
};
const right: backend.FunctionSpec = {
id: "v1",
region: "us-central1",
platform: "gcfv1",
entryPoint: "v1",
...fnMembers,
};

expect(backend.compareFunctions(left, right)).to.eq(0);
});
});
});

0 comments on commit fa450d0

Please sign in to comment.