FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Implements logic to execute deployment plan by joehan · Pull Request #3107 · firebase/firebase-tools · GitHub

Implements logic to execute deployment plan - #3107

Merged
joehan merged 19 commits into
jh-functions-refactorfrom
jh-execute-deployment-plans
Feb 9, 2021
Merged

Implements logic to execute deployment plan#3107
joehan merged 19 commits into
jh-functions-refactorfrom
jh-execute-deployment-plans

Conversation

joehan commented Feb 4, 2021

Copy link
Copy Markdown
Member

Description

Refactors release.ts to build and execute deploymentPlans when deploying functions.

This also changes the release code to use the same operation polling and throttler code as the rest of the codebase. In a follow up PR, I'll get rid of the last few usages of the old polling code (in the functions:delete command), and I'll clean up that code as well.

Scenarios Tested

I've been running a ton of test deploys with this code and I'm going to continue to do so while this PR is under review. Some situations I've tested so far:

  • Deploying 10+ functions across multiple regions, and verifying that sourceToken is used all deploys after the first in a region.
  • Deployments that include deleting functions
  • Updating scheduled pubsub functions into unscheduled ones, and vice versa
  • Deleting scheduled functions.

google-cla Bot added the cla: yes Manual indication that this has passed CLA. label Feb 4, 2021
joehan changed the title Jh execute deployment plans Implements logic to execute deployment plan Feb 4, 2021
joehan requested review from inlined, mbleigh and taeold February 4, 2021 21:10
throw new FirebaseError("Functions did not deploy properly.");
}
// Once everything has been added to queues, starting processing.
// Note: We need to set up these wait before calling process and close.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Figuring this out drove me up a wall for a day and a half :(. It turns out that our implementation of throttler.wait() returns a promise that never resolves if you call it after .process() and .close(). I'd like to fix this at some point, but that code is used in a bunch of other places that I don't understand well enough to test effectively.

Comment thread src/deploy/functions/release.ts Outdated
export interface RegionalDeployment {
region: string;
sourceToken?: string;
firstFunctionDeployment?: () => any;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Realized that I could turn this into an implementation detail of runRegionalFunctionDeployment, so I removed this from the type.

import Queue from "./throttler/queue";

// TODO: Get rid of this when switching to use operation-poller.
// TODO(joehan): Get rid of this once we refactor functions-delete.js

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This file has a mix of random helper functions, and chaff from the old deployment code that I can't quite delete yet because its used on functions-delete and other code paths. I'm gonna make another PR later to clean up the old functions, and move the still relevant functions into a less generically named file inside of the src/deploy/functions directory.

joehan mentioned this pull request Feb 8, 2021
Comment thread src/deploy/functions/checkIam.ts Outdated
export async function checkHttpIam(context: any, options: any, payload: any): Promise<void> {
const functionsInfo = payload.functions.triggers;
const filterGroups = getFilterGroups(options);
const filterGroups = context.filters;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The implementation for getFilterGroups() looks very different from what you've done here. Could you share a bit more insight into why this change was necessary?

(Also - if this change is intended, we could get rid of the import statement on getFilterGroups)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Good catch on the import! Its not the prettiest code flow, but in prepare.ts, we have a line that already does this work: context.filters = getFilterGroups().

I figured it would be nice to avoid redoing this work - however, it does sacrifice some readability, and it could lead to subtle bugs if this code ever gets used elsewhere. I'll make this fall back to getFilterGroups() if context.filters is undefined.

Comment thread src/functionsDelete.js
projectId: projectId,
region: helper.getRegion(name),
functionName,
functionName: name,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I keep getting confused what name refers to in different context.

Say we are given functions resource projects/my-projc/locations/us-central1/functions/myfunc. In this case, name has to bee the fully-qualified name (with project Id & location) right?

If that's right, I'm starting to get worried that this could become a source of pain and bugs. Is that your experience as well?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The Google API style guide defines myfunc as the "id" and projects/my-projc/locations/us-central1/functions/myfunc as the "name". IMO the only solution here is to be pedantically consistent.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This definitely is confusing and ive introduced a few bugs during development because of it. I'll go through and switch everything to id and name to follow the style guide

);
const operationResult = await pollOperation<void>(pollerOptions);
params.timer.endTimer(fnName);
helper.printSuccess(fnName, "delete");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If I'm reading this correctly, I think this will print fully qualified name (including project, region) vs other tasks print function name only. Is that okay?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is working as intended - printSuccess takes a fully qualified name. CloudFunctionTrigger.name is the fully qualified name, so the other tasks behave the same way.

Comment thread src/deploy/functions/tasks.ts Outdated
const defaultPollerOptions = {
apiOrigin: functionsOrigin,
apiVersion: cloudfunctions.API_VERSION,
masterTimeout: 150000,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nit* can we write this as

40 * 60 * 60 // 40 hours

Also - did I get this right? 40 hours looks like a very generous timeout.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If we're guessing what the units are in this timeout that's a good hint that it should be included in the variable name. E.g. masterTimeoutMs

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is in ms, so its 150 seconds. I'll add a comment clarifying this for now. I'd like to make some improvements to throttler at some point in the future since it wasn't the clearest to work with here - when I do so, I'll rename this option to masterTimeoutMs for clarity.

inlined left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

LGTM with the logging fixes Daniel pointed out.

import * as logger from "../../logger";
import { FirebaseError } from "../../error";

type OperationType =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

enum string types make my heart sing. Thanks for this.

Comment thread src/deploy/functions/errorHandler.ts Outdated
for (const failedDep of failedIamCalls) {
logger.info(`\t${failedDep.functionName}`);
}
logger.info("\nUnauthorized users will not be able access this function.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think you mean unauthenticated. One never wants an unauthorized user to have access.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

You are correct! I'm also gonna get @egilmorez to take a look at this to shop the language further at some point.

Comment thread src/deploy/functions/errorHandler.ts Outdated
}
logger.info("\nUnauthorized users will not be able access this function.");
logger.info(
"\nThis may be caused by an organization policy that restricts Network Access on your project."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Or because roles/functions.developer doesn't have functions.setIamPermission on it. You need to be a roles/functions.admin. The required permissions is a major oversight in my opinion, but this is a battle we've already lost.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Ooof, I didn't realize that was the case. I'll reword this to have a bulleted list of common reasons, and include that as the first one.

Comment thread src/deploy/functions/errorHandler.ts Outdated
logger.info("\nTo continue deploying other features (such as database), run:");
logger.info(" " + clc.bold("firebase deploy --except functions"));
// Print all the original messages at debug level.
for (const failedDep of this.errors) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

what does "Dep" mean? I read "dependency" in my head, but it's nothing that starts with "dep" in the struct.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Deployment - I'll replace this with the full word to make this clearer

Comment thread src/deploy/functions/prepare.ts Outdated
import * as clc from "cli-color";

import * as ensureApiEnabled from "../../ensureApiEnabled";
import { ensure, check } from "../../ensureApiEnabled";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

FWIW, I actually preferred the namespaced option since "ensure" and "check" are so generic. Up to you though.

Comment thread src/deploy/functions/tasks.ts Outdated
const defaultPollerOptions = {
apiOrigin: functionsOrigin,
apiVersion: cloudfunctions.API_VERSION,
masterTimeout: 150000,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If we're guessing what the units are in this timeout that's a good hint that it should be included in the variable name. E.g. masterTimeoutMs

"..."
);
params.timer.startTimer(fn.name, "create");
const eventType = fn.eventTrigger ? fn.eventTrigger.eventType : "https";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Just to make sure I understand the latest JS, could this have been written fn?.eventTrigger?.eventType || "https"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yep, it could have been written either way

Comment thread src/deploy/functions/tasks.ts Outdated
export function createFunctionTask(
params: TaskParams,
fn: CloudFunctionTrigger,
onPoll?: (op: any) => any

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is any synchronous callback to be invoked when a polling operation happens? Surely we can at least type the argument to the function. And I'm assuming the return type is void?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Ahh yes, this was an oversight - will fix

joehan requested review from inlined and taeold February 9, 2021 01:15
region: helper.getRegion(fn.name),
eventType: eventType,
functionName: helper.getFunctionName(fn.name),
functionName: helper.getFunctionId(fn.name),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Not for this PR for we should change functionName argument to functionId soon? I volunteer to do that later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Ahhh, this one slipped past me. I like the idea of doing this in a separate PR later - it should be easier to track down any places we missed it then

Comment thread src/functionsDelete.js
const scheduleName = helper.getScheduleName(name, appEngineLocation);
const topicName = helper.getTopicName(name);
const functionName = helper.getFunctionName(name);
const functionName = helper.getFunctionId(name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Same here - functionName vs Id is getting tricky.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

+100 on these - i just found another bug caused by this. I was accidentally using name instead of ID for a call to setIamPolicy, and didn't notice until now because that call was failing for me anyway due to the org policy.

joehan merged commit 8d3d82d into jh-functions-refactor Feb 9, 2021
joehan deleted the jh-execute-deployment-plans branch February 9, 2021 20:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

3 participants


Back | FazBrowse Home | New Git URL