Skip to content

Commit

Permalink
Encapsulate runtime-dependent code
Browse files Browse the repository at this point in the history
Node JS code is now behind /deploy/functions/runtimes/node. The rest
of the codebase now only interacts with the runtime through a
RuntimeDelegate interface exposed in /deploy/functions/runtimes.

All Runtime specific code from backend.ts (and functions API code +
pacakge.json parser) has been deduplicated in runtimes/index.ts

The following files/functions have been renamed:
checkFirebaseSDKVersions.ts -> deploy/functions/runtimes/node/versioning.ts
deploy/functions/discovery/jsexports/* -> deploy/functions/runtimes/node
deploy/functions/checkRuntimeDependencies.ts -> deploy/functions/ensureCloudBuildEnabled.ts
deploy/functions/validate.ts ->
deploy/functions/runtimes/node/validate.ts (Node methods only)

Some code has been refactored to be more coehsive or to remove
redundancies:

1. Multiple places loaded the current firebase-functions SDK. This is
   now called by the RuntimeDelegate and passed where it is needed
2. Context no longer needs anything to do with runtimes. All
   Runtime-dependent code is handled in prepare
3. prepareFunctionsUplaod did a _lot_ more than just pacakaging source.
   It was previously getting the backend (and storing it in context so
   it could be reused), getting env vars, getting runtime config, etc.
   It now just uploads source and everything else is in prepare
   direclty.
4. The min-SDK version check has been moved from getRuntimeChoice()
   to a versioning.ts:checkFunctionsSDKVersion, which always did
   other checks. This saved an exec to npm.

Some small fixes/improvements along the way:

1. The log line we print when a runtime is not specified now tells
   customers to use the latest nodejs runtime
2. We warn customers of breaking changes for any breaking change,
   not just 0.x to 1.x.
  • Loading branch information
inlined committed Jun 5, 2021
1 parent 723b245 commit 5a947b3
Show file tree
Hide file tree
Showing 29 changed files with 632 additions and 556 deletions.
111 changes: 0 additions & 111 deletions src/checkFirebaseSDKVersion.ts

This file was deleted.

2 changes: 0 additions & 2 deletions src/commands/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const { requireDatabaseInstance } = require("../requireDatabaseInstance");
const { requirePermissions } = require("../requirePermissions");
const { checkServiceAccountIam } = require("../deploy/functions/checkIam");
const checkValidTargetFilters = require("../checkValidTargetFilters");
const checkFunctionsSDKVersion = require("../checkFirebaseSDKVersion").checkFunctionsSDKVersion;
const { Command } = require("../command");
const deploy = require("../deploy");
const requireConfig = require("../requireConfig");
Expand Down Expand Up @@ -79,7 +78,6 @@ module.exports = new Command("deploy")
}
})
.before(checkValidTargetFilters)
.before(checkFunctionsSDKVersion)
.action(function (options) {
return deploy(options.filteredTargets, options);
});
2 changes: 1 addition & 1 deletion src/deploy/functions/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ReadStream } from "fs";

import * as backend from "./backend";
import * as gcfV2 from "../../gcp/cloudfunctionsv2";
import * as runtimes from "./runtimes";

// These types should proably be in a root deploy.ts, but we can only boil the ocean one bit at a time.

Expand All @@ -21,7 +22,6 @@ export interface Context {

// Filled in the "prepare" phase.
functionsSource?: string;
runtimeChoice?: backend.Runtime;
runtimeConfigEnabled?: boolean;
firebaseConfig?: FirebaseConfig;

Expand Down
17 changes: 2 additions & 15 deletions src/deploy/functions/backend.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import * as proto from "../../gcp/proto";
import * as gcf from "../../gcp/cloudfunctions";
import * as gcfV2 from "../../gcp/cloudfunctionsv2";
import * as cloudscheduler from "../../gcp/cloudscheduler";
import * as utils from "../../utils";
import * as runtimes from "./runtimes";
import { FirebaseError } from "../../error";
import { Context } from "./args";
import { logger } from "../../logger";
import { previews } from "../../previews";

/** Retry settings for a ScheduleSpec. */
Expand Down Expand Up @@ -116,18 +115,6 @@ export function memoryOptionDisplayName(option: MemoryOptions): string {

export const SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });

/** Supported runtimes for new Cloud Functions. */
export type Runtime = "nodejs10" | "nodejs12" | "nodejs14";

/** Runtimes that can be found in existing backends but not used for new functions. */
export type DeprecatedRuntime = "nodejs6" | "nodejs8";
const RUNTIMES: string[] = ["nodejs10", "nodejs12", "nodejs14"];

/** Type deduction helper for a runtime string. */
export function isValidRuntime(runtime: string): runtime is Runtime {
return RUNTIMES.includes(runtime);
}

/**
* IDs used to identify a regional resource.
* This type exists so we can have lightweight references from a Pub/Sub topic
Expand All @@ -151,7 +138,7 @@ export interface FunctionSpec extends TargetIds {
apiVersion: FunctionsApiVersion;
entryPoint: string;
trigger: HttpsTrigger | EventTrigger;
runtime: Runtime | DeprecatedRuntime;
runtime: runtimes.Runtime | runtimes.DeprecatedRuntime;

labels?: Record<string, string>;
environmentVariables?: Record<string, string>;
Expand Down
53 changes: 0 additions & 53 deletions src/deploy/functions/discovery/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ function isPermissionError(e: { context?: { body?: { error?: { status?: string }
* of the deployed functions.
*
* @param projectId Project ID upon which to check enablement.
* @param runtime The runtime as declared in package.json, e.g. `nodejs10`.
*/
export async function checkRuntimeDependencies(projectId: string, runtime: string): Promise<void> {
export async function ensureCloudBuildEnabled(projectId: string): Promise<void> {
try {
await ensure(projectId, CLOUD_BUILD_API, "functions");
} catch (e) {
Expand Down
47 changes: 23 additions & 24 deletions src/deploy/functions/prepare.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import * as clc from "cli-color";

import { checkRuntimeDependencies } from "./checkRuntimeDependencies";
import { FirebaseError } from "../../error";
import { Options } from "../../options";
import { ensureCloudBuildEnabled } from "./ensureCloudBuildEnabled";
import { functionMatchesAnyGroup, getFilterGroups } from "./functionsDeployHelper";
import { getRuntimeChoice } from "./parseRuntimeAndValidateSDK";
import { prepareFunctionsUpload } from "./prepareFunctionsUpload";
import { promptForFailurePolicies, promptForMinInstances } from "./prompts";
import { logBullet } from "../../utils";
import { getFunctionsConfig, getEnvs, prepareFunctionsUpload } from "./prepareFunctionsUpload";
import { promptForFailurePolicies, promptForMinInstances } from "./prompts";
import * as args from "./args";
import * as backend from "./backend";
import * as ensureApiEnabled from "../../ensureApiEnabled";
import * as functionsConfig from "../../functionsConfig";
import * as getProjectId from "../../getProjectId";
import * as runtimes from "./runtimes";
import * as validate from "./validate";
import { Options } from "../../options";
import { logger } from "../../logger";

export async function prepare(
context: args.Context,
Expand All @@ -24,19 +25,13 @@ export async function prepare(
return;
}

const sourceDirName = options.config.get("functions.source") as string;
if (!sourceDirName) {
throw new FirebaseError(
`No functions code detected at default location (./functions), and no functions.source defined in firebase.json`
);
}
const sourceDir = options.config.path(sourceDirName);
const projectDir = options.config.projectDir;
const projectId = getProjectId(options);
const runtimeDelegate = await runtimes.getRuntimeDelegate(context, options);
logger.debug(`Validating ${runtimeDelegate.name} source`);
await runtimeDelegate.validate();
logger.debug(`Building ${runtimeDelegate.name} source`);
await runtimeDelegate.build();

// Check what runtime to use, first in firebase.json, then in 'engines' field.
const runtimeFromConfig = (options.config.get("functions.runtime") as backend.Runtime) || "";
context.runtimeChoice = getRuntimeChoice(sourceDir, runtimeFromConfig);
const projectId = getProjectId(options);

// Check that all necessary APIs are enabled.
const checkAPIsEnabled = await Promise.all([
Expand All @@ -47,13 +42,22 @@ export async function prepare(
"runtimeconfig",
/* silent=*/ true
),
checkRuntimeDependencies(projectId, context.runtimeChoice!),
ensureCloudBuildEnabled(projectId),
]);
context.runtimeConfigEnabled = checkAPIsEnabled[1];

// Get the Firebase Config, and set it on each function in the deployment.
const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
context.firebaseConfig = firebaseConfig;
const runtimeConfig = await getFunctionsConfig(context);
const env = await getEnvs(context);

logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
const wantBackend = await runtimeDelegate.discoverSpec(runtimeConfig, env);
options.config.set("functions.backend", wantBackend);
if (backend.isEmptyBackend(wantBackend)) {
return;
}

// Prepare the functions directory for upload, and set context.triggers.
logBullet(
Expand All @@ -62,10 +66,8 @@ export async function prepare(
clc.bold(options.config.get("functions.source")) +
" directory for uploading..."
);
context.functionsSource = await prepareFunctionsUpload(context, options);
context.functionsSource = await prepareFunctionsUpload(runtimeConfig, options);

// Get a list of CloudFunctionTriggers.
const wantBackend = options.config.get("functions.backend") as backend.Backend;
// Setup default environment variables on each function.
wantBackend.cloudFunctions.forEach((fn: backend.FunctionSpec) => {
fn.environmentVariables = wantBackend.environmentVariables;
Expand All @@ -91,9 +93,7 @@ export async function prepare(
};

// Validate the function code that is being deployed.
validate.functionsDirectoryExists(options, sourceDirName);
validate.functionIdsAreValid(wantBackend.cloudFunctions);
validate.packageJsonIsValid(sourceDirName, sourceDir, projectDir, !!runtimeFromConfig);

// Check what --only filters have been passed in.
context.filters = getFilterGroups(options);
Expand All @@ -105,6 +105,5 @@ export async function prepare(
const haveFunctions = (await backend.existingBackend(context)).cloudFunctions;
await promptForFailurePolicies(options, wantFunctions, haveFunctions);
await promptForMinInstances(options, wantFunctions, haveFunctions);

await backend.checkAvailability(context, wantBackend);
}
Loading

0 comments on commit 5a947b3

Please sign in to comment.