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

Storage Emulator Enhancement #3809

Merged
merged 14 commits into from
Oct 19, 2021
Merged

Storage Emulator Enhancement #3809

merged 14 commits into from
Oct 19, 2021

Conversation

colerogers
Copy link
Contributor

Description

Storage Emulator Enhancement

@google-cla google-cla bot added the cla: yes Manual indication that this has passed CLA. label Oct 7, 2021
@colerogers colerogers changed the title storage trigger works Storage Emulator Enhancement Oct 7, 2021
@colerogers colerogers marked this pull request as ready for review October 13, 2021 19:18
Copy link
Member

@inlined inlined left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but please make sure someone who is familiar with the emulator code reviews too.

Copy link
Contributor

@taeold taeold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm


export class StorageCloudFunctions {
private logger = EmulatorLogger.forEmulator(Emulators.STORAGE);
private functionsEmulatorInfo?: EmulatorInfo;
private multicastOrigin = "";
private multicastPath = "";
private enabled = false;
private client: Client = new Client({ urlPrefix: "" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we assign a new client object here? It seems like we always create one in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we only initialize in the constructor when functionsEmulator is set, we need to have client be be initialized to some default value (can't be null/undefined) so this seemed like a reasonable place to set a default value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with what we've done here, but from strictly pedantic standpoint, I think it make more sense to let client be an optional member of this class and then add a guard in the dispatch call to exit early to do nothing if we know that the functionsEmulator is not running (i.e. functionsEmulatorInfo is undefined).

src/emulator/storage/cloudFunctions.ts Outdated Show resolved Hide resolved
src/emulator/storage/cloudFunctions.ts Outdated Show resolved Hide resolved
/** Cloud Event */
const ceAction = STORAGE_V2_ACTION_MAP[action];
const data = { ...objectMetadataPayload };
delete (data as any).acl; // remove this field from cloud events
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think owner property needs to be removed.

In fact, maybe we can leverage the existing type definition at https://github.com/googleapis/google-cloudevents-nodejs/blob/main/cloud/storage/v1/StorageObjectData.ts to typecheck the data object, e.g.

const data: StorageObjectData  = { ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is kind of funny, the emulator doesn't actually send back the full object (

export class CloudStorageObjectMetadata {
), just a partial object (both acl & owner are missing).

But you made a good point, I imported StorageObjectData, casted objectMetaDataPayload to unknown then to StorageObjectData (b/c we had some type mismatches)

src/emulator/storage/cloudFunctions.ts Show resolved Hide resolved
Comment on lines 98 to 101
const cloudEvent: string = JSON.stringify({
eventType: `google.cloud.storage.object.v1.${ceAction}`, // need eventType for multicast triggerId
data: ce,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh I'm surprised that this works since I expected the body of the cloudevent http call to look like:

{
  specVersion: 1,
  type: "google.cloud.storage.object.v1..."
  source: "..."
  data: { ... }
}

vs

{
  data: {
    specVersion: 1,
    type: "google.cloud.storage.object.v1..."
    source: "..."
    data: { ... }
  }
}

Something must be unboxing the nested data property before sending it off to the function? Can you help me step through how this works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I had the changes stashed locally from branch switching, I ended up changing it to the comment below, having an eventFormat field and changing the proto based on that.

data,
};
const cloudEvent: string = JSON.stringify({
eventType: `google.cloud.storage.object.v1.${ceAction}`, // need eventType for multicast triggerId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this implementation feels a bit brittle since eventType isn't a thing in a cloudevent payload. It also makes an implicit assumption that the name of the event will always be different between legacy and cloudevent. That is actually, probably true, but it's a subtle assumption that's worth being explicit (either in code or in implementation).

Another way to implement this change would be to teach the multicast handler to understand the difference between cloudevent and legacy event payloads (either by having different endpoint, having a wrapper request body e.g. { eventFormat: "cloudevent", eventBody: body }, etc. Do you think that is going to work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept it as eventType since I wanted to keep multicast handler almost unchanged. But since CloudEvents moved eventType to type we can rely on that to figure out our trigger key. Having a wrapper request makes sense even though it now adds CloudEvents specific logic into the functionsEmulator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of a wrapper, we just build our triggerKey like this:

const triggerKey = proto.type  ? `${this.args.projectId}:${proto.type}` : `${this.args.projectId}:${proto.eventType}`;
const triggers = this.multicastTriggers[triggerKey] || [];

Copy link
Contributor

@taeold taeold Oct 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline - we will probably use the value of the Content-Type header to differentiate cloudevent vs legacy payload.

Also, I think this already works w/o any modification, but I'd make sure that the change we make here is compatible with trigger registration:

addStorageTrigger(projectId: string, key: string, eventTrigger: EventTrigger): boolean {
logger.debug(`addStorageTrigger`, JSON.stringify({ eventTrigger }));
const eventTriggerId = `${projectId}:${eventTrigger.eventType}`;
const triggers = this.multicastTriggers[eventTriggerId] || [];
triggers.push(key);
this.multicastTriggers[eventTriggerId] = triggers;
return true;
}

Copy link
Contributor

@taeold taeold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. You may want to think about adding some test cases in https://github.com/firebase/firebase-tools/tree/master/scripts/triggers-end-to-end-tests for the new storage triggers (old legacy style and new cloudevent style if you are willing to contribute some time).

src/emulator/storage/cloudFunctions.ts Outdated Show resolved Hide resolved
@colerogers
Copy link
Contributor Author

Don't merge until #3829 completes

@colerogers colerogers merged commit 925bdc3 into master Oct 19, 2021
@colerogers colerogers deleted the colerogers.gcs-emulator branch October 19, 2021 16:12
devpeerapong pushed a commit to devpeerapong/firebase-tools that referenced this pull request Dec 14, 2021
* storage trigger works

* removing comment

* cleaning up request body code

* add types

* fixing comments

* removing wrapper, adding check on type field

* using Content-Type header

* linter

* nit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Manual indication that this has passed CLA. perf-h1-2022
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants