Skip to content

Commit

Permalink
Adds test lab triggers to firebase deploy (#5011)
Browse files Browse the repository at this point in the history
* adding in test lab deployment code

* merge changelogs

* updating product name to Test Lab

* fix test
  • Loading branch information
colerogers authored Sep 30, 2022
1 parent 3b4d850 commit 3b48be0
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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"
);
});
});

0 comments on commit 3b48be0

Please sign in to comment.