Skip to content
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

Adds test lab triggers to firebase deploy #5011

Merged
merged 4 commits into from Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,3 +1,4 @@
- Add the "experiments" family of commands (#4994)
- Enable detecting and skipping no-op function deploys (#5032).
- Catches errors when fetching CLI MOTD, allowing process to continue (#4998).
- Adds test lab triggers to firebase deploy (#5011).
16 changes: 15 additions & 1 deletion src/deploy/functions/services/index.ts
Expand Up @@ -6,6 +6,7 @@ import { obtainStorageBindings, ensureStorageTriggerRegion } from "./storage";
import { ensureFirebaseAlertsTriggerRegion } from "./firebaseAlerts";
import { ensureDatabaseTriggerRegion } from "./database";
import { ensureRemoteConfigTriggerRegion } from "./remoteConfig";
import { ensureTestLabTriggerRegion } from "./testLab";

/** A standard void No Op */
export const noop = (): Promise<void> => Promise.resolve();
Expand All @@ -21,7 +22,8 @@ export type Name =
| "firebasealerts"
| "authblocking"
| "database"
| "remoteconfig";
| "remoteconfig"
| "testlab";

/** A service interface for the underlying GCP event services */
export interface Service {
Expand Down Expand Up @@ -104,6 +106,17 @@ const remoteConfigService: Service = {
unregisterTrigger: noop,
};

/** A test lab service object */
const testLabService: Service = {
name: "testlab",
api: "testing.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureTestLabTriggerRegion,
validateTrigger: noop,
registerTrigger: noop,
unregisterTrigger: noop,
};

/** Mapping from event type string to service object */
const EVENT_SERVICE_MAPPING: Record<events.Event, Service> = {
"google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
Expand All @@ -119,6 +132,7 @@ const EVENT_SERVICE_MAPPING: Record<events.Event, Service> = {
"google.firebase.database.ref.v1.updated": databaseService,
"google.firebase.database.ref.v1.deleted": databaseService,
"google.firebase.remoteconfig.remoteConfig.v1.updated": remoteConfigService,
"google.firebase.testlab.testMatrix.v1.completed": testLabService,
};

/**
Expand Down
18 changes: 18 additions & 0 deletions src/deploy/functions/services/testLab.ts
@@ -0,0 +1,18 @@
import * as backend from "../backend";
import { FirebaseError } from "../../../error";

/**
* Sets a Test Lab event trigger's region to 'global' since the service is global
* @param endpoint the test lab endpoint
*/
export function ensureTestLabTriggerRegion(
endpoint: backend.Endpoint & backend.EventTriggered
): Promise<void> {
if (!endpoint.eventTrigger.region) {
endpoint.eventTrigger.region = "global";
}
if (endpoint.eventTrigger.region !== "global") {
throw new FirebaseError("A Test Lab trigger must specify 'global' trigger location");
}
return Promise.resolve();
}
5 changes: 4 additions & 1 deletion src/functions/events/v2.ts
Expand Up @@ -18,9 +18,12 @@ export const DATABASE_EVENTS = [

export const REMOTE_CONFIG_EVENT = "google.firebase.remoteconfig.remoteConfig.v1.updated";

export const TEST_LAB_EVENT = "google.firebase.testlab.testMatrix.v1.completed";

export type Event =
| typeof PUBSUB_PUBLISH_EVENT
| typeof STORAGE_EVENTS[number]
| typeof FIREBASE_ALERTS_PUBLISH_EVENT
| typeof DATABASE_EVENTS[number]
| typeof REMOTE_CONFIG_EVENT;
| typeof REMOTE_CONFIG_EVENT
| typeof TEST_LAB_EVENT;
87 changes: 87 additions & 0 deletions src/test/deploy/functions/checkIam.spec.ts
Expand Up @@ -518,4 +518,91 @@ describe("checkIam", () => {
expect(getIamStub).to.not.have.been.called;
expect(setIamStub).to.not.have.been.called;
});

it("should add the default bindings for a new v2 test lab function without v2 deployed functions", async () => {
const newIamPolicy = {
etag: "etag",
version: 3,
bindings: [
BINDING,
{
role: checkIam.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
members: [
`serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`,
],
},
{
role: checkIam.RUN_INVOKER_ROLE,
members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`],
},
{
role: checkIam.EVENTARC_EVENT_RECEIVER_ROLE,
members: [`serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`],
},
],
};
getIamStub.resolves({
etag: "etag",
version: 3,
bindings: [BINDING],
});
setIamStub.resolves(newIamPolicy);
const wantFn: backend.Endpoint = {
id: "wantFn",
entryPoint: "wantFn",
platform: "gcfv2",
eventTrigger: {
eventType: "google.firebase.testlab.testMatrix.v1.completed",
eventFilters: {},
retry: false,
},
...SPEC,
};

await checkIam.ensureServiceAgentRoles(
projectId,
projectNumber,
backend.of(wantFn),
backend.empty()
);

expect(getIamStub).to.have.been.calledOnce;
expect(setIamStub).to.have.been.calledOnce;
expect(setIamStub).to.have.been.calledWith(projectNumber, newIamPolicy, "bindings");
});

it("should not add bindings for a new v2 test lab function with v2 deployed functions", async () => {
const wantFn: backend.Endpoint = {
id: "wantFn",
entryPoint: "wantFn",
platform: "gcfv2",
eventTrigger: {
eventType: "google.firebase.testlab.testMatrix.v1.completed",
eventFilters: {},
retry: false,
},
...SPEC,
};
const haveFn: backend.Endpoint = {
id: "haveFn",
entryPoint: "haveFn",
platform: "gcfv2",
eventTrigger: {
eventType: "google.cloud.storage.object.v1.finalized",
eventFilters: { bucket: "my-bucket" },
retry: false,
},
...SPEC,
};

await checkIam.ensureServiceAgentRoles(
projectId,
projectNumber,
backend.of(wantFn),
backend.of(haveFn)
);

expect(getIamStub).to.not.have.been.called;
expect(setIamStub).to.not.have.been.called;
});
});
47 changes: 47 additions & 0 deletions src/test/deploy/functions/services/testLab.spec.ts
@@ -0,0 +1,47 @@
import { expect } from "chai";
import { Endpoint } from "../../../../deploy/functions/backend";
import * as testLab from "../../../../deploy/functions/services/testLab";

const projectNumber = "123456789";

const endpoint: Endpoint = {
id: "endpoint",
region: "us-central1",
project: projectNumber,
eventTrigger: {
retry: false,
eventType: "google.firebase.testlab.testMatrix.v1.completed",
eventFilters: {},
},
entryPoint: "endpoint",
platform: "gcfv2",
runtime: "nodejs16",
};

describe("ensureTestLabTriggerRegion", () => {
it("should set the trigger location to global", async () => {
const ep = { ...endpoint };

await testLab.ensureTestLabTriggerRegion(ep);

expect(ep.eventTrigger.region).to.eq("global");
});

it("should not error if the trigger location is global", async () => {
const ep = { ...endpoint };
ep.eventTrigger.region = "global";

await testLab.ensureTestLabTriggerRegion(ep);

expect(ep.eventTrigger.region).to.eq("global");
});

it("should error if the trigger location is not global", () => {
const ep = { ...endpoint };
ep.eventTrigger.region = "us-west1";

expect(() => testLab.ensureTestLabTriggerRegion(ep)).to.throw(
"A Test Lab trigger must specify 'global' trigger location"
);
});
});