Skip to content

Commit

Permalink
Substitute params into backendInfo, and add unit tests (#4328)
Browse files Browse the repository at this point in the history
* Substitute params into backendInfo, and add unit tests

* small pr fixes

* replacing some code i accidentally removed in a merge
  • Loading branch information
joehan committed Mar 21, 2022
1 parent c3931e3 commit daa604a
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 19 deletions.
26 changes: 9 additions & 17 deletions src/emulator/functionsEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
emulatedFunctionsFromEndpoints,
emulatedFunctionsByRegion,
getSecretLocalPath,
toBackendInfo,
} from "./functionsEmulatorShared";
import { EmulatorRegistry } from "./registry";
import { EmulatorLogger, Verbosity } from "./emulatorLogger";
Expand Down Expand Up @@ -833,27 +834,18 @@ export class FunctionsEmulator implements EmulatorInstance {
}

getBackendInfo(): BackendInfo[] {
const cf3Triggers = Object.values(this.triggers)
.filter((t) => !t.backend.extensionInstanceId)
.map((t) => t.def);
const cf3Triggers = this.getCF3Triggers();
return this.args.emulatableBackends.map((e: EmulatableBackend) => {
const envWithSecrets = Object.assign({}, e.env);
for (const s of e.secretEnv) {
envWithSecrets[s.key] = backend.secretVersionName(s);
}

return {
directory: e.functionsDir,
env: envWithSecrets,
extensionInstanceId: e.extensionInstanceId, // Present on all extensions
extension: e.extension, // Only present on published extensions
extensionVersion: e.extensionVersion, // Only present on published extensions
extensionSpec: e.extensionSpec, // Only present on local extensions
functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend.
};
return toBackendInfo(e, cf3Triggers);
});
}

getCF3Triggers(): ParsedTriggerDefinition[] {
return Object.values(this.triggers)
.filter((t) => !t.backend.extensionInstanceId)
.map((t) => t.def);
}

addTriggerRecord(
def: EmulatedTriggerDefinition,
opts: {
Expand Down
40 changes: 39 additions & 1 deletion src/emulator/functionsEmulatorShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import * as fs from "fs";

import * as backend from "../deploy/functions/backend";
import { Constants } from "./constants";
import { EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator";
import { BackendInfo, EmulatableBackend, InvokeRuntimeOpts } from "./functionsEmulator";
import { copyIfPresent } from "../gcp/proto";
import { logger } from "../logger";
import { ENV_DIRECTORY } from "../extensions/manifest";
import { substituteParams } from "../extensions/extensionsHelper";
import { ExtensionSpec, ExtensionVersion } from "../extensions/extensionsApi";

export type SignatureType = "http" | "event" | "cloudevent";

Expand Down Expand Up @@ -391,3 +393,39 @@ export function getSecretLocalPath(backend: EmulatableBackend, projectDir: strin
: backend.functionsDir;
return path.join(secretDirectory, secretsFile);
}

/**
* toBackendInfo transforms an EmulatableBackend into its correspondign API type, BackendInfo
* @param e the emulatableBackend to transform
* @param cf3Triggers a list of CF3 triggers. If e does not include predefinedTriggers, these will be used instead.
*/
export function toBackendInfo(
e: EmulatableBackend,
cf3Triggers: ParsedTriggerDefinition[]
): BackendInfo {
const envWithSecrets = Object.assign({}, e.env);
for (const s of e.secretEnv) {
envWithSecrets[s.key] = backend.secretVersionName(s);
}
let extensionVersion = e.extensionVersion;
if (extensionVersion) {
extensionVersion = substituteParams<ExtensionVersion>(extensionVersion, e.env);
}
let extensionSpec = e.extensionSpec;
if (extensionSpec) {
extensionSpec = substituteParams<ExtensionSpec>(extensionSpec, e.env);
}

// Parse and stringify to get rid of undefined values
return JSON.parse(
JSON.stringify({
directory: e.functionsDir,
env: envWithSecrets,
extensionInstanceId: e.extensionInstanceId, // Present on all extensions
extension: e.extension, // Only present on published extensions
extensionVersion: extensionVersion, // Only present on published extensions
extensionSpec: extensionSpec, // Only present on local extensions
functionTriggers: e.predefinedTriggers ?? cf3Triggers, // If we don't have predefinedTriggers, this is the CF3 backend.
})
);
}
161 changes: 160 additions & 1 deletion src/test/emulators/functionsEmulatorShared.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { expect } from "chai";
import { EmulatableBackend } from "../../emulator/functionsEmulator";
import { BackendInfo, EmulatableBackend } from "../../emulator/functionsEmulator";
import * as functionsEmulatorShared from "../../emulator/functionsEmulatorShared";
import {
Extension,
ExtensionSpec,
ExtensionVersion,
RegistryLaunchStage,
Visibility,
} from "../../extensions/extensionsApi";

const baseDef = {
platform: "gcfv1" as const,
Expand Down Expand Up @@ -126,4 +133,156 @@ describe("FunctionsEmulatorShared", () => {
});
}
});

describe(`${functionsEmulatorShared.toBackendInfo.name}`, () => {
const testCF3Triggers: functionsEmulatorShared.ParsedTriggerDefinition[] = [
{
entryPoint: "cf3",
platform: "gcfv1",
name: "cf3-trigger",
},
];
const testExtTriggers: functionsEmulatorShared.ParsedTriggerDefinition[] = [
{
entryPoint: "ext",
platform: "gcfv1",
name: "ext-trigger",
},
];
const testSpec: ExtensionSpec = {
name: "my-extension",
version: "0.1.0",
resources: [],
sourceUrl: "test.com",
params: [],
postinstallContent: "Should subsitute ${param:KEY}",
};
const testSubbedSpec: ExtensionSpec = {
name: "my-extension",
version: "0.1.0",
resources: [],
sourceUrl: "test.com",
params: [],
postinstallContent: "Should subsitute value",
};
const testExtension: Extension = {
name: "my-extension",
ref: "pubby/my-extensions",
createTime: "",
visibility: Visibility.PUBLIC,
registryLaunchStage: RegistryLaunchStage.BETA,
};
const testExtensionVersion = (spec: ExtensionSpec): ExtensionVersion => {
return {
name: "my-extension",
ref: "pubby/[email protected]",
state: "PUBLISHED",
spec,
hash: "abc123",
sourceDownloadUri: "test.com",
};
};

const tests: {
desc: string;
in: EmulatableBackend;
expected: BackendInfo;
}[] = [
{
desc: "should transform a published Extension backend",
in: {
functionsDir: "test",
env: {
KEY: "value",
},
secretEnv: [],
predefinedTriggers: testExtTriggers,
extension: testExtension,
extensionVersion: testExtensionVersion(testSpec),
extensionInstanceId: "my-instance",
},
expected: {
directory: "test",
env: {
KEY: "value",
},
functionTriggers: testExtTriggers,
extension: testExtension,
extensionVersion: testExtensionVersion(testSubbedSpec),
extensionInstanceId: "my-instance",
},
},
{
desc: "should transform a local Extension backend",
in: {
functionsDir: "test",
env: {
KEY: "value",
},
secretEnv: [],
predefinedTriggers: testExtTriggers,
extensionSpec: testSpec,
extensionInstanceId: "my-local-instance",
},
expected: {
directory: "test",
env: {
KEY: "value",
},
functionTriggers: testExtTriggers,
extensionSpec: testSubbedSpec,
extensionInstanceId: "my-local-instance",
},
},
{
desc: "should transform a CF3 backend",
in: {
functionsDir: "test",
env: {
KEY: "value",
},
secretEnv: [],
},
expected: {
directory: "test",
env: {
KEY: "value",
},
functionTriggers: testCF3Triggers,
},
},
{
desc: "should add secretEnvVar into env",
in: {
functionsDir: "test",
env: {
KEY: "value",
},
secretEnv: [
{
key: "secret",
secret: "asecret",
projectId: "test",
},
],
},
expected: {
directory: "test",
env: {
KEY: "value",
secret: "projects/test/secrets/asecret/versions/latest",
},
functionTriggers: testCF3Triggers,
},
},
];

for (const tc of tests) {
it(tc.desc, () => {
expect(functionsEmulatorShared.toBackendInfo(tc.in, testCF3Triggers)).to.deep.equal(
tc.expected
);
});
}
});
});

0 comments on commit daa604a

Please sign in to comment.