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

Functions deploy uses new "backend" types #3303

Merged
merged 8 commits into from May 4, 2021

Conversation

inlined
Copy link
Member

@inlined inlined commented Apr 23, 2021

Rewrote the Firebase deploy code to use the newly introduced "backend" types. Some smaller things picked up along the way:

  1. Customers can set FIREBASE_FUNCTIONS_DEFAULT_REGION to change the default from "us-central1"
  2. Trigger parsing is moved to deploy/functions/discovery/jsExports. I need to eventually move much of prepare (e.g. Runtime discovery, package.json validation, etc) into discovery or some other directory. The end goal is that we've clearly separated JS code in a place where other languages can be dropped in and to put the JS export analyzing code somwhere it can be SxS with the upcoming HTTP contract.
  3. Some files like functionsDeployHelper.ts have been moved into /deploy/functions to help clean up the swamp that is /src/*.{ts|js}

@inlined inlined requested a review from joehan April 23, 2021 00:24
@google-cla google-cla bot added the cla: yes Manual indication that this has passed CLA. label Apr 23, 2021
if (fn.labels?.["deployment-scheduled"] === "true") {
deployment.schedulesToDelete.push(fn.name);
}
// NOTE(inlined): It's easiest to partition w/ deletes per region and might help
Copy link
Contributor

Choose a reason for hiding this comment

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

When originally implementing this, I started with functionsToDelete as a regional concept, and then turned it into a global one, for a few reasons:

  • We don't need to wait for sourceToken to do deletes, so they're not tied to the rest of the function operations in that region
  • As you mention, we display the full list of functionsToDelete when asking whether we should delete them.

I think the best reason to split them into regions is potential future features - it would make it very easy to add regional filters, for example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Are you saying I should move deletes to global or that I should remove the comment (or incorporate your feedback into the comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, should have been clearer. I'm ok with either implementation. After reviewing the rest of the code the difference is pretty minor - looks like its just a single map().reduce() that you'd be able to get rid of in release.ts if you made functionsToDelete global.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will try to remember to revisit this once the dust settles

@google-cla
Copy link

google-cla bot commented Apr 27, 2021

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

@google-cla google-cla bot added cla: no and removed cla: yes Manual indication that this has passed CLA. labels Apr 27, 2021
@inlined
Copy link
Member Author

inlined commented Apr 27, 2021

@googlebot I consent

@google-cla
Copy link

google-cla bot commented Apr 27, 2021

All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only @googlebot I consent. in this pull request.

Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the cla label to yes (if enabled on your project).

ℹ️ Googlers: Go here for more info.

The Backend namespace defines types that are neither the __trigger
annotations nor the GCP API types. This follows Google best practices
for implementing services because it buffers the system from changes
in storage or API layers.

In this codebase, it will allow us to support new trigger discovery
techniques (e.g. a container contract HTTP API) and new versions
of APIs if they should ever be needed.

In this change I try out a new method for using args.Context; it's
based on the Go style of Context where a namespace can provide
a value using Context as a private implementation detail.
I'm personally a fan. WDYT?
@google-cla google-cla bot added cla: yes Manual indication that this has passed CLA. and removed cla: no labels Apr 28, 2021
options: args.Options,
runtimeConfigValues: backend.RuntimeConfigValues
): Promise<backend.Backend> {
let strategy: Strategy | undefined = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you use this pattern instead of the cleaner let strategy: Strategy? Is it just to specifically call out that strategy can be undefined if we can't find one to use?

Copy link
Member Author

Choose a reason for hiding this comment

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

Effectively, yes. TypeScript is smart enough to know that strategy is uninitialized and may not be initialized below. It becomes a compiler error to check.

@@ -0,0 +1,236 @@
"use strict";
Copy link
Contributor

Choose a reason for hiding this comment

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

Not needed since this is ts

Copy link
Member Author

Choose a reason for hiding this comment

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

Yoinked

reject(
new FirebaseError(
"There was an unknown problem while trying to parse function triggers. " +
"Please ensure you are using Node.js v6 or greater.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems out of date now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Moving this discussion offline. It probably is out of date, but I'm wondering if it's worth removing something that probably protects us from cryptic errors.

new FirebaseError(
"There was an unknown problem while trying to parse function triggers. " +
"Please ensure you are using Node.js v6 or greater.",
{ exit: 2 }
Copy link
Contributor

Choose a reason for hiding this comment

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

Any idea why this is exit code 2? Google says that means "Misuse of shell builtins (according to Bash documentation)", which doesn't really apply.

If we don't have a clear reasoning here, we can probably stop explicitly setting exit code.

Copy link
Member Author

Choose a reason for hiding this comment

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

We seem to use exit code 2 a fair amount. Moving that conversation offline too.

Copy link
Member Author

Choose a reason for hiding this comment

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

As an aside, I apologize that you, for some reason, had to review this whole file even though only the end of the file is new. I guess it tipped the scales enough that Git no longer saw the file as moved + appended.


export function functionMatchesGroup(func: backend.TargetIds, groupChunks: string[]): boolean {
const functionNameChunks = func.id.split("-").slice(0, groupChunks.length);
return _.isEqual(groupChunks, functionNameChunks);
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: Seems only slightly annoying to change this from lodash to plain TS, and this is the last usage of lodash in this file. Removing this would let us get lodash out of another file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

for (const failurePolicy of [undefined, false, true, { retry: {} }]) {
const name =
typeof failurePolicy === "undefined" ? "undefined" : JSON.stringify(failurePolicy);
it(`failurePolicy ${name}`, () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it(`failurePolicy ${name}`, () => {
it(`should handle failurePolicy=${name}`, () => {


const BASIC_FUNCTION_NAME: backend.TargetIds = Object.freeze({
id: "func",
region: "us-central1",
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this test fail if FIREBASE_FUNCTIONS_DEFAULT_REGION is set to something else?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will use a hack to set & reset api.functionsDefaultRegion. Unfortunately this won't work once we have ESM since those are readonly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alternative that should work once we have ESM - just set this to api.functionsDefaultRegion

Copy link
Member Author

Choose a reason for hiding this comment

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

🤦‍♂️ yes. That's obviously better.

};

describe("functionMatchesGroup", () => {
it("should match empty filters", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: repo convention is to separate it blocks with newlines.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looked at other files. The first it comes without a newline after the describe but multiple its have a newline. Applied that style to this file.

@inlined inlined changed the base branch from master to launch.backend-refactor May 1, 2021 01:05
Copy link
Contributor

@joehan joehan left a comment

Choose a reason for hiding this comment

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

A few more things, but this is pretty much LGTM after those.

// Topics aren't created yet explicitly because the Functions API creates them
// automatically. This may change in GCFv2 and would certainly change in Run,
// so we should be ready to start creating topics before schedules or functions.
// OTOH, we could just say that schedules targeting Pub/Sub are just a v1 thing
Copy link
Contributor

Choose a reason for hiding this comment

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

We originally chose pubsub because private HTTPS functions didn't exist yet. As long as we can ensure that only Scheduler can invoke the underlying function, it seems preferable to simplify things in v2 by switching all scheduled functions to use HTTPS as a transport.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, totally get the status quo. I think HTTPS is a better transport since it allows Cloud Scheduler's backff/retry policy to be meaningful, but I don't think we can convert v1 Pub/Sub functions to a v2 HTTP function though (unless we do some special magic...)

export function allRegions(
spec: Record<string, backend.FunctionSpec[]>,
existing: Record<string, backend.FunctionSpec[]>
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Add a return type

Copy link
Member Author

Choose a reason for hiding this comment

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

done

if (haveFn) {
checkForInvalidChangeOfTrigger(fn, haveFn);

// Remember old environment varialbes that might have been set with
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo in the word variables

Copy link
Member Author

Choose a reason for hiding this comment

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

Whoops

// functions across all regions. Should we go back to making functions to delete a global
// part of the DeploymentPlan?
// Alternatively we could make schedules and topics regional based on the service they feed into.
const allFunctionsToDelete: backend.FunctionSpec[] = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Unless I'm totally missing something, allFunctionsToDelete is unused after building it

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right. Maybe I translated this before making it no longer global.

/**
* Removes any inspect options (`inspect` or `inspect-brk`) from options so the forked process is able to run (otherwise
* it'll inherit process values and will use the same port).
* @param {string[]} options From either `process.execArgv` or `NODE_OPTIONS` envar (which is a space separated string)
Copy link
Contributor

Choose a reason for hiding this comment

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

No longer need these {string[]}s since we have real types now

Copy link
Member Author

Choose a reason for hiding this comment

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

done

const invalidNames = _.reject(_.keys(functionNames), (name: string): boolean => {
return _.startsWith(name, ".") || validFunctionNameRegex.test(name);
const invalidIds = functions.filter((fn) => {
// NOTE(inlined): Why is it OK for a function to start with a "."?
Copy link
Contributor

Choose a reason for hiding this comment

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

Following up from the internal discussion - I just tested this out of curiosity, and it errors out on the deploy with : {"error":{"code":400,"message":"The request has errors","status":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.BadRequest","fieldViolations":[{"field":"function","description":"short function name .helloWorld does not match the expected pattern"}]}]}}

We should get rid of this.

)}] Changing from an HTTPS function to an background triggered function is not allowed. Please delete your function and create a new one instead.`
);
}
if (fn.eventTrigger?.service != exFn.eventTrigger?.service) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the explanation! Right when i joined, I spent some time debugging some GCF + CF3 tests that wouldn't work on staging, and I distinctly remember being super confused by why preprod talked to prod services.

Anecdotes aside, makes sense to me to deprecate this.


const BASIC_FUNCTION_NAME: backend.TargetIds = Object.freeze({
id: "func",
region: "us-central1",
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternative that should work once we have ESM - just set this to api.functionsDefaultRegion

Copy link
Contributor

@joehan joehan left a comment

Choose a reason for hiding this comment

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

LGTM!

@inlined inlined merged commit 192d514 into launch.backend-refactor May 4, 2021
inlined added a commit that referenced this pull request May 4, 2021
Add backend namespace before rebasing codebase onto it.

The Backend namespace defines types that are neither the __trigger
annotations nor the GCP API types. This follows Google best practices
for implementing services because it buffers the system from changes
in storage or API layers.

In this codebase, it will allow us to support new trigger discovery
techniques (e.g. a container contract HTTP API) and new versions
of APIs if they should ever be needed.

In this change I try out a new method for using args.Context; it's
based on the Go style of Context where a namespace can provide
a value using Context as a private implementation detail.
I'm personally a fan. WDYT?
@inlined inlined deleted the inlined.gcf-typing branch May 5, 2021 01:47
inlined added a commit that referenced this pull request May 10, 2021
Add backend namespace before rebasing codebase onto it.

The Backend namespace defines types that are neither the __trigger
annotations nor the GCP API types. This follows Google best practices
for implementing services because it buffers the system from changes
in storage or API layers.

In this codebase, it will allow us to support new trigger discovery
techniques (e.g. a container contract HTTP API) and new versions
of APIs if they should ever be needed.

In this change I try out a new method for using args.Context; it's
based on the Go style of Context where a namespace can provide
a value using Context as a private implementation detail.
I'm personally a fan. WDYT?
inlined added a commit that referenced this pull request May 14, 2021
…tructs (#3361)

* Functions deploy uses new "backend" types (#3303)

Add backend namespace before rebasing codebase onto it.

The Backend namespace defines types that are neither the __trigger
annotations nor the GCP API types. This follows Google best practices
for implementing services because it buffers the system from changes
in storage or API layers.

In this codebase, it will allow us to support new trigger discovery
techniques (e.g. a container contract HTTP API) and new versions
of APIs if they should ever be needed.

In this change I try out a new method for using args.Context; it's
based on the Go style of Context where a namespace can provide
a value using Context as a private implementation detail.
I'm personally a fan. WDYT?

* Add format converters for the new backend flags (#3333)

* Wire the new flags into the backend refactor (#3334)

Wire up new flag to the backend refactor

* Create and Update Function should add deploymentTool labels (#3356)

Function create and update should add deployment tool labels

* Add prompts for new runWith options (#3352)

Add CLI prompts for the new runWith options

Also accidentally merged:
Fix issues from bug bash

1. Remove the "No HTTPS functions" log line is no longer printed
2. The list of function URLs that are printed is now restricted to
   this deploy.
devpeerapong pushed a commit to devpeerapong/firebase-tools that referenced this pull request Dec 14, 2021
…tructs (firebase#3361)

* Functions deploy uses new "backend" types (firebase#3303)

Add backend namespace before rebasing codebase onto it.

The Backend namespace defines types that are neither the __trigger
annotations nor the GCP API types. This follows Google best practices
for implementing services because it buffers the system from changes
in storage or API layers.

In this codebase, it will allow us to support new trigger discovery
techniques (e.g. a container contract HTTP API) and new versions
of APIs if they should ever be needed.

In this change I try out a new method for using args.Context; it's
based on the Go style of Context where a namespace can provide
a value using Context as a private implementation detail.
I'm personally a fan. WDYT?

* Add format converters for the new backend flags (firebase#3333)

* Wire the new flags into the backend refactor (firebase#3334)

Wire up new flag to the backend refactor

* Create and Update Function should add deploymentTool labels (firebase#3356)

Function create and update should add deployment tool labels

* Add prompts for new runWith options (firebase#3352)

Add CLI prompts for the new runWith options

Also accidentally merged:
Fix issues from bug bash

1. Remove the "No HTTPS functions" log line is no longer printed
2. The list of function URLs that are printed is now restricted to
   this deploy.
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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants