-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Major refactor of functions deploy #3132
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
06b62a1
Adding onPoll option to operation-poller (#3046)
joehan 6258dd5
Typescriptify functionsDeployHelper (#3059)
joehan ec7d079
Typescriptifying gcp.cloudfunctions (#3060)
joehan f583ef6
Typescriptifying functionsConfig (#3063)
joehan 08b9d56
Typescriptifying deploymentTool (#3061)
joehan b4944a4
Refactoring prepare stage of functions deploy (#3067)
joehan 3be0dca
refactoring release step of functions deploy to use typescript
joehan e0e703e
Adding logic to build regional deployments
joehan 046c7d7
Implementing createDeploymentPlan
joehan b876523
First round of PR feedback, removing most usages of lodash
joehan 9e0e6e9
moving function prompts into their own file
joehan 2a3b547
seperating out a bunch of code from functionsDeployHelper
joehan 51f2395
Resolves merge conflicts
joehan 30cc0e9
refactoring release step of functions deploy to use typescript (#3071)
joehan 6916000
Implements core logic of running function deploys
joehan 3c8d4a0
Typescriptifying prepareFunctionsUpload (#3064)
joehan 11956fa
Implementing createDeploymentPlan (#3081)
joehan 85d0afe
adding timing and logs for deployments
joehan 00b1989
cleaning up unused code
joehan 397d7c4
Fixing some things that were broken while merging
joehan 21f4906
Fixing up the order of wait and close to ensure that queue promsies a…
joehan 3b3edbd
Format and clean up typos
joehan e428bcb
refactoring error handling to be cleaner
joehan 4c8e2fb
cleaning up extera newlines
joehan 7f48130
first round of pr fixes
joehan 39a7e86
Readding some changes that I accidenttally wiped out during a merge
joehan 1366955
Switching name to id where appropriate
joehan 7513229
fixing another bug caused by functionName vs Id
joehan 8d3d82d
Merge pull request #3107 from firebase/jh-execute-deployment-plans
joehan 6d2260e
Refactor functions-delete (#3110)
joehan 42e6c15
Cleaning up error reporting
joehan e4ce126
Merge remote-tracking branch 'public/master' into jh-functions-refactor
joehan 12a48ea
Merge remote-tracking branch 'public/master' into jh-functions-refactor
joehan 7cfe9d9
Implement validation for changing trigger types, and fixes from bug b…
joehan 5eb08bd
Merge branch 'master' into jh-functions-refactor
joehan 5ca6bbf
Merge branch 'master' into jh-functions-refactor
joehan 344b674
fixes package.json
joehan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Implementing createDeploymentPlan (#3081)
* refactoring release step of functions deploy to use typescript * Adding logic to build regional deployments * Implementing createDeploymentPlan * First round of PR feedback, removing most usages of lodash * moving function prompts into their own file * seperating out a bunch of code from functionsDeployHelper * round of pr fixes * adresses more pr comments, and adds some todos
- Loading branch information
commit 11956fa55683ff82581ef742ef6c72fa0d6c4a31
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import * as deploymentTool from "../../deploymentTool"; | ||
| import { functionMatchesAnyGroup, getTopicName } from "../../functionsDeployHelper"; | ||
|
|
||
| // TODO: Better name for this? | ||
| // It's really a CloudFuntion, not just a trigger, | ||
| // but CloudFunction is a different exported type from firebase-functions | ||
| export interface CloudFunctionTrigger { | ||
| name: string; | ||
| sourceUploadUrl?: string; | ||
| sourceToken?: string; | ||
| labels: { [key: string]: string }; | ||
| environmentVariables: { [key: string]: string }; | ||
| entryPoint: string; | ||
| runtime?: string; | ||
| vpcConnector?: string; | ||
| vpcConnectorEgressSettings?: string; | ||
| ingressSettings?: string; | ||
| availableMemoryMb?: number; | ||
| timeout?: number; | ||
| maxInstances?: number; | ||
| serviceAccountEmail?: string; | ||
| httpsTrigger?: any; | ||
| eventTrigger?: any; | ||
| failurePolicy?: {}; | ||
| schedule?: object; | ||
| timeZone?: string; | ||
| regions?: string[]; | ||
| } | ||
|
|
||
| export interface RegionMap { | ||
| [region: string]: CloudFunctionTrigger[]; | ||
| } | ||
|
|
||
| export interface RegionalDeployment { | ||
| region: string; | ||
| sourceToken?: string; | ||
| firstFunctionDeployment?: () => any; | ||
| functionsToCreate: CloudFunctionTrigger[]; | ||
| functionsToUpdate: CloudFunctionTrigger[]; | ||
| schedulesToUpsert: CloudFunctionTrigger[]; | ||
| } | ||
|
|
||
| export interface DeploymentPlan { | ||
| regionalDeployments: RegionalDeployment[]; | ||
| functionsToDelete: string[]; | ||
| schedulesToDelete: string[]; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a map of regions to all the CloudFunctions being deployed | ||
| * to that region. | ||
| * @param projectId The project in use. | ||
| * @param localFunctions A list of all CloudFunctions in the deployment. | ||
| */ | ||
| export function functionsByRegion( | ||
| projectId: string, | ||
| localFunctions: CloudFunctionTrigger[] | ||
| ): RegionMap { | ||
| const regionMap: RegionMap = {}; | ||
| for (const trigger of localFunctions) { | ||
| if (!trigger.regions) { | ||
| trigger.regions = ["us-central1"]; | ||
| } | ||
| // Create a separate CloudFunction for | ||
| // each region we deploy a function to | ||
| for (const region of trigger.regions) { | ||
| const triggerDeepCopy = JSON.parse(JSON.stringify(trigger)); | ||
| if (triggerDeepCopy.regions) { | ||
| delete triggerDeepCopy.regions; | ||
| } | ||
| triggerDeepCopy.name = [ | ||
| "projects", | ||
| projectId, | ||
| "locations", | ||
| region, | ||
| "functions", | ||
| trigger.name, | ||
| ].join("/"); | ||
| regionMap[region] = regionMap[region] || []; | ||
| regionMap[region].push(triggerDeepCopy); | ||
| } | ||
| } | ||
| return regionMap; | ||
| } | ||
|
|
||
| /** | ||
| * Helper method to turn a RegionMap into a flat list of all functions in a deployment. | ||
| * @param regionMap A RegionMap for the deployment. | ||
| */ | ||
| export function allFunctions(regionMap: RegionMap): CloudFunctionTrigger[] { | ||
| const triggers: CloudFunctionTrigger[] = []; | ||
| for (const [k, v] of Object.entries(regionMap)) { | ||
| triggers.push(...v); | ||
| } | ||
| return triggers; | ||
| } | ||
|
|
||
| /** | ||
| * Create a plan for deploying all functions in one region. | ||
| * @param region The region of this deployment | ||
| * @param loclFunctionsByRegion The functions present in the code currently being deployed. | ||
| * @param existingFunctionNames The names of all functions that already exist. | ||
| * @param existingScheduledFunctionNames The names of all schedules functions that already exist. | ||
| * @param filters The filters, passed in by the user via `--only functions:` | ||
| */ | ||
| export function createDeploymentPlan( | ||
| localFunctionsByRegion: RegionMap, | ||
| existingFunctions: CloudFunctionTrigger[], | ||
| filters: string[][] | ||
| ): DeploymentPlan { | ||
| let existingFnsCopy: CloudFunctionTrigger[] = [...existingFunctions]; | ||
| const deployment: DeploymentPlan = { | ||
| regionalDeployments: [], | ||
| functionsToDelete: [], | ||
| schedulesToDelete: [], | ||
| }; | ||
| // eslint-disable-next-line guard-for-in | ||
| for (const region in localFunctionsByRegion) { | ||
| const regionalDeployment: RegionalDeployment = { | ||
| region, | ||
| functionsToCreate: [], | ||
| functionsToUpdate: [], | ||
| schedulesToUpsert: [], | ||
| }; | ||
| const localFunctionsInRegion = localFunctionsByRegion[region]; | ||
| for (const fn of localFunctionsInRegion) { | ||
| // Check if this function matches the --only filters | ||
| if (!functionMatchesAnyGroup(fn.name, filters)) { | ||
| continue; | ||
| } | ||
| // Check if this local function has the same name as an exisiting one. | ||
| const matchingExistingFunction = existingFnsCopy.find((exFn) => exFn.name === fn.name); | ||
| // Check if the matching exisitng function is scheduled | ||
| const isMatchingExisitingFnScheduled = | ||
| matchingExistingFunction?.labels?.["deployment-scheduled"] === "true"; | ||
| // Check if the local function is a scheduled function | ||
| if (fn.schedule) { | ||
| // If the local function is scheduled, set its trigger to the correct pubsub topic | ||
| fn.eventTrigger.resource = getTopicName(fn.name); | ||
| // and create or update a schedule. | ||
| regionalDeployment.schedulesToUpsert.push(fn); | ||
| } else if (isMatchingExisitingFnScheduled) { | ||
| // If the local function isn't scheduled but the existing one is, delete the schedule. | ||
| deployment.schedulesToDelete.push(matchingExistingFunction!.name); | ||
| } | ||
|
|
||
| if (matchingExistingFunction) { | ||
| regionalDeployment.functionsToUpdate.push(fn); | ||
| existingFnsCopy = existingFnsCopy.filter((exFn: CloudFunctionTrigger) => { | ||
| return exFn.name !== fn.name; | ||
| }); | ||
| } else { | ||
| regionalDeployment.functionsToCreate.push(fn); | ||
| } | ||
| } | ||
| deployment.regionalDeployments.push(regionalDeployment); | ||
| } | ||
|
|
||
| // Delete any remaining existing functions that: | ||
| // 1 - Have the deployment-tool: 'firebase-cli' label and | ||
| // 2 - Match the --only filters, if any are provided. | ||
| const functionsToDelete = existingFnsCopy | ||
| .filter((fn) => { | ||
| return deploymentTool.isFirebaseManaged(fn.labels); | ||
| }) | ||
| .filter((fn) => { | ||
| return functionMatchesAnyGroup(fn.name, filters); | ||
| }); | ||
| deployment.functionsToDelete = functionsToDelete.map((fn) => { | ||
| return fn.name; | ||
| }); | ||
| // Also delete any schedules for functions that we are deleting. | ||
| for (const fn of functionsToDelete) { | ||
| if (fn.labels?.["deployment-scheduled"] === "true") { | ||
| deployment.schedulesToDelete.push(fn.name); | ||
| } | ||
| } | ||
| return deployment; | ||
| } | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import * as clc from "cli-color"; | ||
|
|
||
| import { getFunctionLabel } from "../../functionsDeployHelper"; | ||
| import { CloudFunctionTrigger } from "./deploymentPlanner"; | ||
| import { FirebaseError } from "../../error"; | ||
| import { promptOnce } from "../../prompt"; | ||
| import * as utils from "../../utils"; | ||
|
|
||
| /** | ||
| * Checks if a deployment will create any functions with a failure policy. | ||
| * If there are any, prompts the user to acknowledge the retry behavior. | ||
| * @param options | ||
| * @param functions A list of all functions in the deployment | ||
| */ | ||
| export async function promptForFailurePolicies( | ||
| options: any, | ||
| functions: CloudFunctionTrigger[] | ||
| ): Promise<void> { | ||
| // Collect all the functions that have a retry policy | ||
| const failurePolicyFunctions = functions.filter((fn: CloudFunctionTrigger) => { | ||
| return !!fn.failurePolicy; | ||
| }); | ||
|
|
||
| if (failurePolicyFunctions.length === 0) { | ||
| return; | ||
| } | ||
| const failurePolicyFunctionLabels = failurePolicyFunctions.map((fn: CloudFunctionTrigger) => { | ||
| return getFunctionLabel(fn.name); | ||
| }); | ||
| const retryMessage = | ||
| "The following functions will be retried in case of failure: " + | ||
| clc.bold(failurePolicyFunctionLabels.join(", ")) + | ||
| ". " + | ||
| "Retried executions are billed as any other execution, and functions are retried repeatedly until they either successfully execute or the maximum retry period has elapsed, which can be up to 7 days. " + | ||
| "For safety, you might want to ensure that your functions are idempotent; see https://firebase.google.com/docs/functions/retries to learn more."; | ||
|
|
||
| utils.logLabeledWarning("functions", retryMessage); | ||
|
|
||
| if (options.force) { | ||
| return; | ||
| } else if (options.nonInteractive) { | ||
| throw new FirebaseError("Pass the --force option to deploy functions with a failure policy", { | ||
| exit: 1, | ||
| }); | ||
| } | ||
| const proceed = await promptOnce({ | ||
| type: "confirm", | ||
| name: "confirm", | ||
| default: false, | ||
| message: "Would you like to proceed with deployment?", | ||
| }); | ||
| if (!proceed) { | ||
| throw new FirebaseError("Deployment canceled.", { exit: 1 }); | ||
| } | ||
| } |
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
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joehan how about
CloudFunctionConfig?